Browse Source

feat(gui-v2,deps): update attachment cell styles

# What's changed?

* add a "button" into empty cell to add items
* add a dropzone to cell
pull/2972/head
braks 2 years ago
parent
commit
1189ec02eb
  1. 178
      packages/nc-gui-v2/components/cell/Attachment.vue
  2. 2
      packages/nc-gui-v2/components/smartsheet/Cell.vue
  3. 2
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  4. 4
      packages/nc-gui-v2/composables/useViewColumns.ts

178
packages/nc-gui-v2/components/cell/Attachment.vue

@ -1,15 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import { inject, reactive, ref, useProject, watchEffect } from '#imports' import { computed, inject, ref, useApi, useDropZone, useFileDialog, useProject, watch } from '#imports'
import { useNuxtApp } from '#app'
import { ColumnInj, EditModeInj, MetaInj } from '~/context' import { ColumnInj, EditModeInj, MetaInj } from '~/context'
import { NOCO } from '~/lib' import { NOCO } from '~/lib'
import { isImage } from '~/utils' import { isImage } from '~/utils'
import MaterialPlusIcon from '~icons/mdi/plus' import MaterialSymbolsAttachFile from '~icons/material-symbols/attach-file'
import MaterialArrowExpandIcon from '~icons/mdi/arrow-expand' import MaterialArrowExpandIcon from '~icons/mdi/arrow-expand'
import MaterialSymbolsFileCopyOutline from '~icons/material-symbols/file-copy-outline'
interface Props { interface Props {
modelValue: string | any[] | null modelValue: string | Record<string, any>[] | null
} }
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
@ -17,27 +17,44 @@ const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const isPublicForm = inject<boolean>('isPublicForm', false) const isPublicForm = inject<boolean>('isPublicForm', false)
const isForm = inject<boolean>('isForm', false) const isForm = inject<boolean>('isForm', false)
const meta = inject(MetaInj)
const column = inject(ColumnInj) const meta = inject(MetaInj)!
const column = inject(ColumnInj)!
const editEnabled = inject(EditModeInj, ref(false)) const editEnabled = inject(EditModeInj, ref(false))
const localFilesState = reactive([])
const attachments = ref([]) const attachments = ref([])
const uploading = ref(false) const uploading = ref(false)
const fileInput = ref<HTMLInputElement>()
const { $api } = useNuxtApp() const dropZoneRef = ref<HTMLDivElement>()
const { api } = useApi()
const { project } = useProject() const { project } = useProject()
const toast = useToast() const { files, open, reset } = useFileDialog()
watchEffect(() => { const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
if (modelValue) {
attachments.value = ((typeof modelValue === 'string' ? JSON.parse(modelValue) : modelValue) || []).filter(Boolean) watch(
() => modelValue,
(nextModel) => {
if (nextModel) {
attachments.value = ((typeof nextModel === 'string' ? JSON.parse(nextModel) : nextModel) || []).filter(Boolean)
}
},
)
function onDrop(droppedFiles: File[] | null) {
if (droppedFiles) {
// set files
console.log(droppedFiles)
} }
}) }
const selectImage = (file: any, i: unknown) => { const selectImage = (file: any, i: unknown) => {
// todo: implement // todo: implement
@ -47,11 +64,9 @@ const openUrl = (url: string, target = '_blank') => {
window.open(url, target) window.open(url, target)
} }
const addFile = () => {
fileInput.value?.click()
}
const onFileSelection = async (e: unknown) => { const onFileSelection = async (e: unknown) => {
if (!files.value) return
// if (this.isPublicGrid) { // if (this.isPublicGrid) {
// return // return
// } // }
@ -76,90 +91,85 @@ const onFileSelection = async (e: unknown) => {
// return // return
// } // }
// todo : move to com
uploading.value = true uploading.value = true
const newAttachments = [] const newAttachments = []
for (const file of fileInput.value?.files ?? []) {
for (const file of files.value) {
try { try {
const data = await $api.storage.upload( const data = await api.storage.upload(
{ {
path: [NOCO, project.value.title, meta?.value?.title, column?.title].join('/'), path: [NOCO, project.value.title, meta.value.title, column.title].join('/'),
}, },
{ {
files: file, files: file,
json: '{}', json: '{}',
}, },
) )
newAttachments.push(...data) newAttachments.push(...data)
} catch (e: any) { } catch (e: any) {
toast.error(e.message || 'Some internal error occurred') notification.error({
uploading.value = false message: e.message || 'Some internal error occurred',
return })
} }
} }
uploading.value = false uploading.value = false
emit('update:modelValue', JSON.stringify([...attachments.value, ...newAttachments])) emit('update:modelValue', JSON.stringify([...attachments.value, ...newAttachments]))
} }
watch(files, console.log)
const items = computed(() => (isPublicForm ? files.value : attachments.value) || [])
</script> </script>
<template> <template>
<div class="h-full w-full"> <div class="flex items-center">
<div class="flex items-center img-container"> <div ref="dropZoneRef" class="flex-1 group color-transition flex items-center p-1 hover:text-primary">
<div class="d-flex no-overflow"> <template v-if="isOverDropZone">
<div <div
v-for="(item, i) in isPublicForm ? localFilesState : attachments" class="w-full h-full flex items-center justify-center p-1 rounded gap-1 bg-gradient-to-t from-primary/10 via-primary/25 to-primary/10 !text-primary"
:key="item.url || item.title"
class="thumbnail align-center justify-center d-flex"
> >
<!-- <v-tooltip bottom> --> <MaterialSymbolsFileCopyOutline class="text-pink-500" /> Drop here
<!-- <template #activator="{ on }"> --> </div>
<!-- <v-img </template>
v-if="isImage(item.title, item.mimetype)" <template v-else>
lazy-src="https://via.placeholder.com/60.png?text=Loading..." <div class="flex overflow-hidden">
alt="#" <div v-for="(item, i) of items" :key="item.url || item.title" class="thumbnail align-center justify-center d-flex">
max-height="99px" <img
contain v-if="isImage(item.title, item.mimetype)"
:src="item.url || item.data" alt="#"
v-on="on" style="max-height: 30px; max-width: 30px"
@click="selectImage(item.url || item.data, i)" :src="item.url || item.data"
> --> @click="selectImage(item.url || item.data, i)"
<img />
v-if="isImage(item.title, item.mimetype)" <v-icon v-else-if="item.icon" :size="active ? 33 : 22" v-on="on" @click="openUrl(item.url || item.data, '_blank')">
alt="#" {{ item.icon }}
style="max-height: 30px; max-width: 30px" </v-icon>
:src="item.url || item.data" <v-icon v-else :size="active ? 33 : 22" v-on="on" @click="openUrl(item.url || item.data, '_blank')">
@click="selectImage(item.url || item.data, i)" mdi-file
/> </v-icon>
<!-- <template #placeholder> --> </div>
<!-- <v-skeleton-loader type="image" :height="active ? 33 : 22" :width="active ? 33 : 22" /> -->
<!-- </template> -->
<v-icon v-else-if="item.icon" :size="active ? 33 : 22" v-on="on" @click="openUrl(item.url || item.data, '_blank')">
{{ item.icon }}
</v-icon>
<v-icon v-else :size="active ? 33 : 22" v-on="on" @click="openUrl(item.url || item.data, '_blank')"> mdi-file </v-icon>
<!-- </template> -->
<!-- <span>{{ item.title }}</span> -->
<!-- </v-tooltip> -->
</div> </div>
</div>
<!-- todo: hide or toggle based on ancestor -->
<div class="add d-flex align-center justify-center px-1 nc-attachment-add" @click="addFile">
<v-icon v-if="uploading" small color="primary" class="nc-attachment-add-spinner"> mdi-loading mdi-spin</v-icon>
<!-- <v-btn v-else-if="isForm" outlined x-small color="" text class="nc-attachment-add-btn">
<v-icon x-small color="" icon="MaterialPlusIcon"> mdi-plus </v-icon>
Attachment
</v-btn>
<v-icon small color="primary nc-attachment-add-icon">
mdi-plus
</v-icon> -->
<MaterialPlusIcon />
</div>
<MaterialArrowExpandIcon @click.stop="dialog = true" />
<!-- <v-icon class="expand-icon mr-1" x-small color="primary" @click.stop="dialog = true"> mdi-arrow-expand </v-icon> -->
</div>
<input ref="fileInput" type="file" multiple class="hidden" @change="onFileSelection" /> <!-- todo: hide or toggle based on ancestor -->
<div class="mx-auto flex gap-1 items-center active:ring rounded border-1 py-1 px-4" @click.stop="open">
<v-icon v-if="uploading" small color="primary" class="nc-attachment-add-spinner"> mdi-loading mdi-spin</v-icon>
<a-tooltip placement="bottom">
<template #title> Click or drop a file into cell </template>
<div class="flex items-center gap-2">
<MaterialSymbolsAttachFile class="transform group-hover:(text-pink-500 scale-120)" />
<div v-if="!items.length">Add file(s)</div>
</div>
</a-tooltip>
</div>
<MaterialArrowExpandIcon v-if="items.length" @click.stop />
</template>
</div>
</div> </div>
</template> </template>
@ -175,14 +185,4 @@ const onFileSelection = async (e: unknown) => {
max-width: 33px; max-width: 33px;
} }
} }
.expand-icon {
margin-left: 8px;
border-radius: 2px;
transition: 0.3s background-color;
}
.expand-icon:hover {
background-color: var(--v-primary-lighten4);
}
</style> </style>

2
packages/nc-gui-v2/components/smartsheet/Cell.vue

@ -89,7 +89,7 @@ div {
} }
.nc-cell { .nc-cell {
position: relative; @apply relative w-full h-full;
} }
.nc-locked-overlay { .nc-locked-overlay {

2
packages/nc-gui-v2/components/smartsheet/Grid.vue

@ -177,7 +177,7 @@ if (meta) useProvideColumnCreateStore(meta)
padding: 0 5px; padding: 0 5px;
& > * { & > * {
@apply flex align-center h-auto; @apply flex items-center;
} }
overflow: hidden; overflow: hidden;
} }

4
packages/nc-gui-v2/composables/useViewColumns.ts

@ -28,8 +28,8 @@ export function useViewColumns(
if (!meta || !view) return if (!meta || !view) return
let order = 1 let order = 1
if (view?.value?.id) { if (view.value?.id) {
const data = (await $api.dbViewColumn.list(view?.value?.id)) as any[] const data = (await $api.dbViewColumn.list(view.value.id)) as any[]
const fieldById = data.reduce<Record<string, any>>((acc, curr) => { const fieldById = data.reduce<Record<string, any>>((acc, curr) => {
curr.show = !!curr.show curr.show = !!curr.show

Loading…
Cancel
Save