mirror of https://github.com/nocodb/nocodb
Pranav C
2 years ago
51 changed files with 4048 additions and 1514 deletions
@ -1,11 +0,0 @@ |
|||||||
<script setup lang="ts"> |
|
||||||
import { inject } from '@vue/runtime-core' |
|
||||||
|
|
||||||
const value = inject<boolean | number>('value') |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div class="d-flex align-center"> |
|
||||||
<input v-model="value" type="checkbox" :disabled="true" /> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,6 +1,6 @@ |
|||||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||||
import type { ColumnType } from 'nocodb-sdk' |
import type { ColumnType } from 'nocodb-sdk' |
||||||
import { computed } from 'vue' |
import { computed, inject } from 'vue' |
||||||
|
|
||||||
const { modelValue: value } = defineProps<{ modelValue: any }>() |
const { modelValue: value } = defineProps<{ modelValue: any }>() |
||||||
const emit = defineEmits(['update:modelValue']) |
const emit = defineEmits(['update:modelValue']) |
@ -1,19 +0,0 @@ |
|||||||
<script> |
|
||||||
import dayjs from 'dayjs' |
|
||||||
|
|
||||||
export default { |
|
||||||
name: 'DateCell', |
|
||||||
props: ['value'], |
|
||||||
computed: { |
|
||||||
date() { |
|
||||||
return (/^\d+$/.test(this.value) ? dayjs(+this.value) : dayjs(this.value)).format('YYYY-MM-DD') |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<span>{{ date }}</span> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped></style> |
|
@ -1,21 +0,0 @@ |
|||||||
<script> |
|
||||||
import dayjs from 'dayjs' |
|
||||||
|
|
||||||
export default { |
|
||||||
name: 'DateTimeCell', |
|
||||||
props: ['value'], |
|
||||||
computed: { |
|
||||||
dateTime() { |
|
||||||
return !this.value |
|
||||||
? this.value |
|
||||||
: (/^\d+$/.test(this.value) ? dayjs(+this.value) : dayjs(this.value)).format('YYYY-MM-DD HH:mm') |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<span>{{ dateTime }}</span> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped></style> |
|
@ -1,32 +0,0 @@ |
|||||||
<script setup lang="ts"> |
|
||||||
import type { ColumnType } from 'nocodb-sdk' |
|
||||||
import { inject } from 'vue' |
|
||||||
import { enumColor } from '~/utils/colorsUtils' |
|
||||||
|
|
||||||
const colors = enumColor.light |
|
||||||
|
|
||||||
const value = inject('value') |
|
||||||
const column = inject<ColumnType>('column') |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<span |
|
||||||
v-for="v in [(value || '').replace(/\\'/g, '\'').replace(/^'|'$/g, '')]" |
|
||||||
:key="v" |
|
||||||
:style="{ |
|
||||||
background: colors[v], |
|
||||||
}" |
|
||||||
class="set-item ma-1 py-1 px-3" |
|
||||||
>{{ v }}</span |
|
||||||
> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped> |
|
||||||
.set-item { |
|
||||||
display: inline-block; |
|
||||||
border-radius: 25px; |
|
||||||
white-space: nowrap; |
|
||||||
} |
|
||||||
</style> |
|
@ -1,12 +0,0 @@ |
|||||||
<script> |
|
||||||
export default { |
|
||||||
name: 'JsonCell', |
|
||||||
props: ['value'], |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<pre class="text-left caption">{{ value }}</pre> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped></style> |
|
@ -1,5 +1,6 @@ |
|||||||
<script> |
<script> |
||||||
import MonacoJsonObjectEditor from '@/components/monaco/MonacoJsonObjectEditor' |
import MonacoJsonObjectEditor from '@/components/monaco/MonacoJsonObjectEditor' |
||||||
|
const editEnabled = inject < boolean > 'editEnabled' |
||||||
|
|
||||||
export default { |
export default { |
||||||
name: 'JsonEditableCell', |
name: 'JsonEditableCell', |
@ -0,0 +1,126 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { computed } from '@vue/reactivity' |
||||||
|
import type { ColumnType } from 'nocodb-sdk' |
||||||
|
import { Ref, inject } from 'vue' |
||||||
|
import { enumColor } from '~/utils/colorsUtils' |
||||||
|
|
||||||
|
const { modelValue } = defineProps<{ modelValue: any }>() |
||||||
|
const emit = defineEmits(['update:modelValue']) |
||||||
|
const column = inject<ColumnType>('column') |
||||||
|
const isForm = inject<boolean>('isForm') |
||||||
|
const editEnabled = inject<boolean>('editEnabled') |
||||||
|
|
||||||
|
const options = computed<string[]>(() => { |
||||||
|
return column?.dtxp?.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) || [] |
||||||
|
}) |
||||||
|
|
||||||
|
const localState = computed({ |
||||||
|
get() { |
||||||
|
return modelValue?.match(/(?:[^',]|\\')+(?='?(?:,|$))/g).map((v: string) => v.replace(/\\'/g, "'")) |
||||||
|
}, |
||||||
|
set(val) { |
||||||
|
emit('update:modelValue', val.filter((v: string) => options.value.includes(v)).join(',')) |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
/* import colors from '@/components/project/spreadsheet/helpers/colors' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'SetListCheckboxCell', |
||||||
|
props: { |
||||||
|
value: String, |
||||||
|
column: Object, |
||||||
|
values: Array, |
||||||
|
}, |
||||||
|
data() {}, |
||||||
|
computed: { |
||||||
|
colors() { |
||||||
|
return this.$store.state.settings.darkTheme ? colors.dark : colors.light |
||||||
|
}, |
||||||
|
localState: { |
||||||
|
get() { |
||||||
|
return (this.value && this.value.split(',')) || [] |
||||||
|
}, |
||||||
|
set(val) { |
||||||
|
this.$emit('input', val.join(',')) |
||||||
|
this.$emit('update') |
||||||
|
}, |
||||||
|
}, |
||||||
|
setValues() { |
||||||
|
if (this.column && this.column.dtxp) { |
||||||
|
return this.column.dtxp.split(',').map((v) => v.replace(/^'|'$/g, '')) |
||||||
|
} |
||||||
|
return this.values || [] |
||||||
|
}, |
||||||
|
parentListeners() { |
||||||
|
const $listeners = {} |
||||||
|
|
||||||
|
if (this.$listeners.blur) { |
||||||
|
$listeners.blur = this.$listeners.blur |
||||||
|
} |
||||||
|
if (this.$listeners.focus) { |
||||||
|
$listeners.focus = this.$listeners.focus |
||||||
|
} |
||||||
|
|
||||||
|
return $listeners |
||||||
|
}, |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.$el.focus() |
||||||
|
const event = document.createEvent('MouseEvents') |
||||||
|
event.initMouseEvent('mousedown', true, true, window) |
||||||
|
this.$el.dispatchEvent(event) |
||||||
|
}, |
||||||
|
} */ |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<!-- |
||||||
|
<v-select |
||||||
|
v-model="localState" |
||||||
|
:items="options" |
||||||
|
hide-details |
||||||
|
:clearable="!column.rqd" |
||||||
|
variation="outlined" |
||||||
|
multiple |
||||||
|
/> |
||||||
|
--> |
||||||
|
|
||||||
|
<v-combobox |
||||||
|
v-model="localState" |
||||||
|
:items="options" |
||||||
|
multiple |
||||||
|
chips |
||||||
|
flat |
||||||
|
dense |
||||||
|
solo |
||||||
|
hide-details |
||||||
|
deletable-chips |
||||||
|
class="text-center mt-0" |
||||||
|
> |
||||||
|
<!-- <template #selection="data"> --> |
||||||
|
<!-- <v-chip --> |
||||||
|
<!-- :key="data.item" --> |
||||||
|
<!-- small --> |
||||||
|
<!-- class="ma-1 " --> |
||||||
|
<!-- :color="colors[setValues.indexOf(data.item) % colors.length]" --> |
||||||
|
<!-- @click:close="data.parent.selectItem(data.item)" --> |
||||||
|
<!-- > --> |
||||||
|
<!-- {{ data.item }} --> |
||||||
|
<!-- </v-chip> --> |
||||||
|
<!-- </template> --> |
||||||
|
|
||||||
|
<!-- <template #item="{item}"> --> |
||||||
|
<!-- <v-chip small :color="colors[setValues.indexOf(item) % colors.length]"> --> |
||||||
|
<!-- {{ item }} --> |
||||||
|
<!-- </v-chip> --> |
||||||
|
<!-- </template> --> |
||||||
|
<!-- <template #append> --> |
||||||
|
<!-- <v-icon small class="mt-2"> --> |
||||||
|
<!-- mdi-menu-down --> |
||||||
|
<!-- </v-icon> --> |
||||||
|
<!-- </template> --> |
||||||
|
</v-combobox> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped></style> |
@ -1,68 +0,0 @@ |
|||||||
<script> |
|
||||||
// import colors from '@/mixins/colors' |
|
||||||
|
|
||||||
export default { |
|
||||||
name: 'SetListCell', |
|
||||||
// mixins: [colors], |
|
||||||
props: ['value', 'column'], |
|
||||||
computed: { |
|
||||||
setValues() { |
|
||||||
if (this.column && this.column.dtxp) |
|
||||||
return this.column.dtxp.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) |
|
||||||
|
|
||||||
return [] |
|
||||||
}, |
|
||||||
selectedValues() { |
|
||||||
return this.value ? this.value.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) : [] |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<v-chip |
|
||||||
v-for="v in selectedValues" |
|
||||||
v-show="v || setValues.includes(v)" |
|
||||||
:key="v" |
|
||||||
small |
|
||||||
:color="colors[setValues.indexOf(v) % colors.length]" |
|
||||||
class="set-item ma-1 py-1 px-3" |
|
||||||
> |
|
||||||
{{ v }} |
|
||||||
</v-chip> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped> |
|
||||||
/* |
|
||||||
.set-item { |
|
||||||
display: inline-block; |
|
||||||
border-radius: 25px; |
|
||||||
white-space: nowrap; |
|
||||||
}*/ |
|
||||||
</style> |
|
||||||
<!-- |
|
||||||
/** |
|
||||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
|
||||||
* |
|
||||||
* @author Naveen MR <oof1lab@gmail.com> |
|
||||||
* @author Pranav C Balan <pranavxc@gmail.com> |
|
||||||
* |
|
||||||
* @license GNU AGPL version 3 or any later version |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU Affero General Public License as |
|
||||||
* published by the Free Software Foundation, either version 3 of the |
|
||||||
* License, or (at your option) any later version. |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU Affero General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU Affero General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
* |
|
||||||
*/ |
|
||||||
--> |
|
@ -1,17 +1,51 @@ |
|||||||
<script> |
<script setup lang="ts"> |
||||||
export default { |
import { inject } from 'vue' |
||||||
name: 'TimeCell', |
|
||||||
props: ['value'], |
const editEnabled = inject<boolean>('editEnabled') |
||||||
computed: { |
|
||||||
time() { |
|
||||||
return typeof this.value === 'string' ? this.value.replace(/(\d)T(?=\d)/, '$1 ') : this.value |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
</script> |
</script> |
||||||
|
|
||||||
<template> |
<template> |
||||||
<span>{{ time }}</span> |
<v-menu> |
||||||
|
<template #activator="{ on }"> |
||||||
|
<input v-model="localState" class="value" v-on="on" /> |
||||||
|
</template> |
||||||
|
<div class="d-flex flex-column justify-center" @click.stop> |
||||||
|
<v-time-picker v-model="localState" v-on="parentListeners" /> |
||||||
|
<v-btn small color="primary" @click="$emit('save')"> |
||||||
|
<!-- Save --> |
||||||
|
{{ $t('general.save') }} |
||||||
|
</v-btn> |
||||||
|
</div> |
||||||
|
</v-menu> |
||||||
</template> |
</template> |
||||||
|
|
||||||
<style scoped></style> |
<style scoped> |
||||||
|
.value { |
||||||
|
width: 100%; |
||||||
|
min-height: 20px; |
||||||
|
} |
||||||
|
</style> |
||||||
|
<!-- |
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Naveen MR <oof1lab@gmail.com> |
||||||
|
* @author Pranav C Balan <pranavxc@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
--> |
||||||
|
@ -1,20 +1,83 @@ |
|||||||
<script> |
<script> |
||||||
import { isValidURL } from '~/helpers' |
const editEnabled = inject < boolean > 'editEnabled' |
||||||
|
|
||||||
|
/* |
||||||
|
import { isValidURL } from '@/helpers' |
||||||
|
import { inject } from "vue"; |
||||||
|
|
||||||
export default { |
export default { |
||||||
name: 'UrlCell', |
name: 'EditableUrlCell', |
||||||
props: ['value'], |
props: { |
||||||
|
value: String, |
||||||
|
column: Object, |
||||||
|
}, |
||||||
computed: { |
computed: { |
||||||
isValid() { |
localState: { |
||||||
return this.value && isValidURL(this.value) |
get() { |
||||||
|
return this.value |
||||||
}, |
}, |
||||||
|
set(val) { |
||||||
|
if (!(this.column && this.column.meta && this.column.meta.validate) || isValidURL(val)) { |
||||||
|
this.$emit('input', val) |
||||||
|
} |
||||||
}, |
}, |
||||||
} |
}, |
||||||
|
parentListeners() { |
||||||
|
const $listeners = {} |
||||||
|
|
||||||
|
if (this.$listeners.blur) { |
||||||
|
$listeners.blur = this.$listeners.blur |
||||||
|
} |
||||||
|
if (this.$listeners.focus) { |
||||||
|
$listeners.focus = this.$listeners.focus |
||||||
|
} |
||||||
|
|
||||||
|
if (this.$listeners.cancel) { |
||||||
|
$listeners.cancel = this.$listeners.cancel |
||||||
|
} |
||||||
|
|
||||||
|
return $listeners |
||||||
|
}, |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.$el.focus() |
||||||
|
}, |
||||||
|
} */ |
||||||
</script> |
</script> |
||||||
|
|
||||||
<template> |
<template> |
||||||
<a v-if="isValid" :href="value" target="_blank">{{ value }}</a> |
<input v-model="localState" v-on="parentListeners" /> |
||||||
<span v-else>{{ value }}</span> |
|
||||||
</template> |
</template> |
||||||
|
|
||||||
<style scoped></style> |
<style scoped> |
||||||
|
input, |
||||||
|
textarea { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
color: var(--v-textColor-base); |
||||||
|
} |
||||||
|
</style> |
||||||
|
<!-- |
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Naveen MR <oof1lab@gmail.com> |
||||||
|
* @author Pranav C Balan <pranavxc@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
--> |
||||||
|
@ -1,335 +0,0 @@ |
|||||||
<script setup lang="ts"> |
|
||||||
import { useNuxt } from '@nuxt/kit' |
|
||||||
import { inject, watchEffect } from '@vue/runtime-core' |
|
||||||
import type { ColumnType, TableType } from 'nocodb-sdk' |
|
||||||
import type { Ref } from 'vue' |
|
||||||
import { useToast } from 'vue-toastification' |
|
||||||
import { useNuxtApp } from '#app' |
|
||||||
import useProject from '~/composables/useProject' |
|
||||||
// import FileSaver from "file-saver"; |
|
||||||
import { isImage } from '~/utils/fileUtils' |
|
||||||
import MaterialPlusIcon from '~icons/mdi/plus' |
|
||||||
import MaterialArrowExpandIcon from '~icons/mdi/arrow-expand' |
|
||||||
|
|
||||||
const { modelValue } = defineProps<{ modelValue: string | Array<any> }>() |
|
||||||
const emit = defineEmits(['update:modelValue']) |
|
||||||
|
|
||||||
const isPublicForm = inject<boolean>('isPublicForm', false) |
|
||||||
const isForm = inject<boolean>('isForm', false) |
|
||||||
const meta = inject<Ref<TableType>>('meta') |
|
||||||
const column = inject<ColumnType>('column') |
|
||||||
|
|
||||||
const localFilesState = reactive([]) |
|
||||||
const attachments = ref([]) |
|
||||||
const uploading = ref(false) |
|
||||||
const fileInput = ref<HTMLInputElement>() |
|
||||||
|
|
||||||
const { $api } = useNuxtApp() |
|
||||||
const { project } = useProject() |
|
||||||
const toast = useToast() |
|
||||||
|
|
||||||
watchEffect(() => { |
|
||||||
if (modelValue) { |
|
||||||
attachments.value = ((typeof modelValue === 'string' ? JSON.parse(modelValue) : modelValue) || []).filter(Boolean) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
const selectImage = (file: any, i) => { |
|
||||||
// todo: implement |
|
||||||
} |
|
||||||
|
|
||||||
const openUrl = (url: string, target = '_blank') => { |
|
||||||
window.open(url, target) |
|
||||||
} |
|
||||||
|
|
||||||
const addFile = () => { |
|
||||||
fileInput.value?.click() |
|
||||||
} |
|
||||||
|
|
||||||
const onFileSelection = async (e) => { |
|
||||||
// if (this.isPublicGrid) { |
|
||||||
// return |
|
||||||
// } |
|
||||||
// if (!this.$refs.file.files || !this.$refs.file.files.length) { |
|
||||||
// return |
|
||||||
// } |
|
||||||
|
|
||||||
// if (this.isPublicForm) { |
|
||||||
// this.localFilesState.push(...Array.from(this.$refs.file.files).map((file) => { |
|
||||||
// const res = { file, title: file.name } |
|
||||||
// if (isImage(file.name, file.mimetype)) { |
|
||||||
// const reader = new FileReader() |
|
||||||
// reader.onload = (e) => { |
|
||||||
// this.$set(res, 'data', e.target.result) |
|
||||||
// } |
|
||||||
// reader.readAsDataURL(file) |
|
||||||
// } |
|
||||||
// return res |
|
||||||
// })) |
|
||||||
// |
|
||||||
// this.$emit('input', this.localFilesState.map(f => f.file)) |
|
||||||
// return |
|
||||||
// } |
|
||||||
|
|
||||||
// todo : move to com |
|
||||||
uploading.value = true |
|
||||||
const newAttachments = [] |
|
||||||
for (const file of fileInput.value?.files ?? []) { |
|
||||||
try { |
|
||||||
const data = await $api.storage.upload( |
|
||||||
{ |
|
||||||
path: ['noco', project.value.title, meta?.value?.title, column?.title].join('/'), |
|
||||||
}, |
|
||||||
{ |
|
||||||
files: file, |
|
||||||
json: '{}', |
|
||||||
}, |
|
||||||
) |
|
||||||
newAttachments.push(...data) |
|
||||||
} catch (e: any) { |
|
||||||
toast.error(e.message || 'Some internal error occurred') |
|
||||||
uploading.value = false |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
uploading.value = false |
|
||||||
emit('update:modelValue', JSON.stringify([...attachments.value, ...newAttachments])) |
|
||||||
|
|
||||||
// this.$emit('input', JSON.stringify(this.localState)) |
|
||||||
// this.$emit('update') |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div class="main h-100"> |
|
||||||
<div class="d-flex align-center img-container"> |
|
||||||
<div class="d-flex no-overflow"> |
|
||||||
<div |
|
||||||
v-for="(item, i) in isPublicForm ? localFilesState : attachments" |
|
||||||
:key="item.url || item.title" |
|
||||||
class="thumbnail align-center justify-center d-flex" |
|
||||||
> |
|
||||||
<!-- <v-tooltip bottom> --> |
|
||||||
<!-- <template #activator="{ on }"> --> |
|
||||||
<!-- <v-img |
|
||||||
v-if="isImage(item.title, item.mimetype)" |
|
||||||
lazy-src="https://via.placeholder.com/60.png?text=Loading..." |
|
||||||
alt="#" |
|
||||||
max-height="99px" |
|
||||||
contain |
|
||||||
:src="item.url || item.data" |
|
||||||
v-on="on" |
|
||||||
@click="selectImage(item.url || item.data, i)" |
|
||||||
> --> |
|
||||||
<img |
|
||||||
v-if="isImage(item.title, item.mimetype)" |
|
||||||
alt="#" |
|
||||||
style="max-height: 30px; max-width: 30px" |
|
||||||
:src="item.url || item.data" |
|
||||||
@click="selectImage(item.url || item.data, i)" |
|
||||||
/> |
|
||||||
<!-- <template #placeholder> --> |
|
||||||
<!-- <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> |
|
||||||
<!-- 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> |
|
||||||
|
|
||||||
<v-spacer /> |
|
||||||
|
|
||||||
<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="d-none" @change="onFileSelection" /> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped lang="scss"> |
|
||||||
.thumbnail { |
|
||||||
height: 30px; |
|
||||||
width: 30px; |
|
||||||
margin: 2px; |
|
||||||
border-radius: 4px; |
|
||||||
|
|
||||||
img { |
|
||||||
max-height: 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); |
|
||||||
} |
|
||||||
|
|
||||||
/*.img-container { |
|
||||||
margin: 0 -2px; |
|
||||||
} |
|
||||||
|
|
||||||
.no-overflow { |
|
||||||
overflow: hidden; |
|
||||||
} |
|
||||||
|
|
||||||
.add { |
|
||||||
transition: 0.2s background-color; |
|
||||||
!*background-color: #666666ee;*! |
|
||||||
border-radius: 4px; |
|
||||||
height: 33px; |
|
||||||
margin: 5px 2px; |
|
||||||
} |
|
||||||
|
|
||||||
.add:hover { |
|
||||||
!*background-color: #66666699;*! |
|
||||||
} |
|
||||||
|
|
||||||
.thumbnail { |
|
||||||
height: 99px; |
|
||||||
width: 99px; |
|
||||||
margin: 2px; |
|
||||||
border-radius: 4px; |
|
||||||
} |
|
||||||
|
|
||||||
.thumbnail img { |
|
||||||
!*max-height: 33px;*! |
|
||||||
max-width: 99px; |
|
||||||
} |
|
||||||
|
|
||||||
.main { |
|
||||||
min-height: 20px; |
|
||||||
position: relative; |
|
||||||
height: auto; |
|
||||||
} |
|
||||||
|
|
||||||
.expand-icon { |
|
||||||
margin-left: 8px; |
|
||||||
border-radius: 2px; |
|
||||||
!*opacity: 0;*! |
|
||||||
transition: 0.3s background-color; |
|
||||||
} |
|
||||||
|
|
||||||
.expand-icon:hover { |
|
||||||
!*opacity: 1;*! |
|
||||||
background-color: var(--v-primary-lighten4); |
|
||||||
} |
|
||||||
|
|
||||||
.modal-thumbnail img { |
|
||||||
height: 50px; |
|
||||||
max-width: 100%; |
|
||||||
border-radius: 4px; |
|
||||||
} |
|
||||||
|
|
||||||
.modal-thumbnail { |
|
||||||
position: relative; |
|
||||||
margin: 10px 10px; |
|
||||||
} |
|
||||||
|
|
||||||
.remove-icon { |
|
||||||
position: absolute; |
|
||||||
top: 5px; |
|
||||||
right: 5px; |
|
||||||
} |
|
||||||
|
|
||||||
.modal-thumbnail-card { |
|
||||||
.download-icon { |
|
||||||
position: absolute; |
|
||||||
bottom: 5px; |
|
||||||
right: 5px; |
|
||||||
opacity: 0; |
|
||||||
transition: 0.4s opacity; |
|
||||||
} |
|
||||||
|
|
||||||
&:hover .download-icon { |
|
||||||
opacity: 1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.image-overlay-container { |
|
||||||
max-height: 100vh; |
|
||||||
overflow-y: auto; |
|
||||||
position: relative; |
|
||||||
} |
|
||||||
|
|
||||||
.image-overlay-container .close-icon { |
|
||||||
position: fixed; |
|
||||||
top: 15px; |
|
||||||
right: 15px; |
|
||||||
} |
|
||||||
|
|
||||||
.overlay-thumbnail { |
|
||||||
transition: 0.4s transform, 0.4s opacity; |
|
||||||
opacity: 0.5; |
|
||||||
} |
|
||||||
|
|
||||||
.overlay-thumbnail.active { |
|
||||||
transform: scale(1.4); |
|
||||||
opacity: 1; |
|
||||||
} |
|
||||||
|
|
||||||
.overlay-thumbnail:hover { |
|
||||||
opacity: 1; |
|
||||||
} |
|
||||||
|
|
||||||
.modal-title { |
|
||||||
text-overflow: ellipsis; |
|
||||||
white-space: nowrap; |
|
||||||
width: 100%; |
|
||||||
overflow: hidden; |
|
||||||
} |
|
||||||
|
|
||||||
.modal-thumbnail-card { |
|
||||||
transition: 0.4s transform; |
|
||||||
} |
|
||||||
|
|
||||||
.modal-thumbnail-card:hover { |
|
||||||
transform: scale(1.05); |
|
||||||
} |
|
||||||
|
|
||||||
.drop-overlay { |
|
||||||
z-index: 5; |
|
||||||
position: absolute; |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
left: 0; |
|
||||||
right: 0; |
|
||||||
top: 0; |
|
||||||
bottom: 5px; |
|
||||||
background: #aaaaaa44; |
|
||||||
display: flex; |
|
||||||
justify-content: center; |
|
||||||
align-items: center; |
|
||||||
pointer-events: none; |
|
||||||
} |
|
||||||
|
|
||||||
.expand-icon { |
|
||||||
opacity: 0; |
|
||||||
transition: 0.4s opacity; |
|
||||||
} |
|
||||||
|
|
||||||
.main:hover .expand-icon { |
|
||||||
opacity: 1; |
|
||||||
}*/ |
|
||||||
</style> |
|
@ -1,138 +0,0 @@ |
|||||||
<script> |
|
||||||
import { convertDurationToSeconds, convertMS2Duration, durationOptions } from '~/helpers/durationHelper' |
|
||||||
|
|
||||||
export default { |
|
||||||
name: 'DurationCell', |
|
||||||
props: { |
|
||||||
column: Object, |
|
||||||
value: [Number, String], |
|
||||||
readOnly: Boolean, |
|
||||||
}, |
|
||||||
data: () => ({ |
|
||||||
// flag to determine to show warning message or not |
|
||||||
showWarningMessage: false, |
|
||||||
// duration in milliseconds |
|
||||||
durationInMS: null, |
|
||||||
// check if the cell is edited or not |
|
||||||
isEdited: false, |
|
||||||
}), |
|
||||||
computed: { |
|
||||||
localState: { |
|
||||||
get() { |
|
||||||
return convertMS2Duration(this.value, this.durationType) |
|
||||||
}, |
|
||||||
set(val) { |
|
||||||
this.isEdited = true |
|
||||||
const res = convertDurationToSeconds(val, this.durationType) |
|
||||||
if (res._isValid) { |
|
||||||
this.durationInMS = res._sec |
|
||||||
} |
|
||||||
}, |
|
||||||
}, |
|
||||||
durationPlaceholder() { |
|
||||||
return durationOptions[this.durationType].title |
|
||||||
}, |
|
||||||
durationType() { |
|
||||||
return this.column?.meta?.duration || 0 |
|
||||||
}, |
|
||||||
parentListeners() { |
|
||||||
const $listeners = {} |
|
||||||
|
|
||||||
if (this.$listeners.blur) { |
|
||||||
$listeners.blur = this.$listeners.blur |
|
||||||
} |
|
||||||
if (this.$listeners.focus) { |
|
||||||
$listeners.focus = this.$listeners.focus |
|
||||||
} |
|
||||||
|
|
||||||
return $listeners |
|
||||||
}, |
|
||||||
}, |
|
||||||
mounted() { |
|
||||||
window.addEventListener('keypress', (_) => { |
|
||||||
if (this.$refs.durationInput) { |
|
||||||
this.$refs.durationInput.focus() |
|
||||||
} |
|
||||||
}) |
|
||||||
}, |
|
||||||
methods: { |
|
||||||
checkDurationFormat(evt) { |
|
||||||
evt = evt || window.event |
|
||||||
const charCode = evt.which ? evt.which : evt.keyCode |
|
||||||
// ref: http://www.columbia.edu/kermit/ascii.html |
|
||||||
const PRINTABLE_CTL_RANGE = charCode > 31 |
|
||||||
const NON_DIGIT = charCode < 48 || charCode > 57 |
|
||||||
const NON_COLON = charCode !== 58 |
|
||||||
const NON_PERIOD = charCode !== 46 |
|
||||||
if (PRINTABLE_CTL_RANGE && NON_DIGIT && NON_COLON && NON_PERIOD) { |
|
||||||
this.showWarningMessage = true |
|
||||||
evt.preventDefault() |
|
||||||
} else { |
|
||||||
this.showWarningMessage = false |
|
||||||
// only allow digits, '.' and ':' (without quotes) |
|
||||||
return true |
|
||||||
} |
|
||||||
}, |
|
||||||
onBlur() { |
|
||||||
if (this.isEdited) { |
|
||||||
this.$emit('input', this.durationInMS) |
|
||||||
} |
|
||||||
this.isEdited = false |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div class="duration-cell-wrapper"> |
|
||||||
<input |
|
||||||
ref="durationInput" |
|
||||||
v-model="localState" |
|
||||||
:placeholder="durationPlaceholder" |
|
||||||
@blur="onBlur" |
|
||||||
@keypress="checkDurationFormat($event)" |
|
||||||
@keydown.enter="isEdited && $emit('input', durationInMS)" |
|
||||||
v-on="parentListeners" |
|
||||||
/> |
|
||||||
<div v-if="showWarningMessage == true" class="duration-warning"> |
|
||||||
<!-- TODO: i18n --> |
|
||||||
Please enter a number |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped> |
|
||||||
.duration-cell-wrapper { |
|
||||||
padding: 10px; |
|
||||||
} |
|
||||||
|
|
||||||
.duration-warning { |
|
||||||
text-align: left; |
|
||||||
margin-top: 10px; |
|
||||||
color: #e65100; |
|
||||||
} |
|
||||||
</style> |
|
||||||
|
|
||||||
<!-- |
|
||||||
/** |
|
||||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
|
||||||
* |
|
||||||
* @author Wing-Kam Wong <wingkwong.code@gmail.com> |
|
||||||
* |
|
||||||
* @license GNU AGPL version 3 or any later version |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU Affero General Public License as |
|
||||||
* published by the Free Software Foundation, either version 3 of the |
|
||||||
* License, or (at your option) any later version. |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU Affero General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU Affero General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
* |
|
||||||
*/ |
|
||||||
--> |
|
@ -1,79 +0,0 @@ |
|||||||
<script> |
|
||||||
import { isValidURL } from '@/helpers' |
|
||||||
|
|
||||||
export default { |
|
||||||
name: 'EditableUrlCell', |
|
||||||
props: { |
|
||||||
value: String, |
|
||||||
column: Object, |
|
||||||
}, |
|
||||||
computed: { |
|
||||||
localState: { |
|
||||||
get() { |
|
||||||
return this.value |
|
||||||
}, |
|
||||||
set(val) { |
|
||||||
if (!(this.column && this.column.meta && this.column.meta.validate) || isValidURL(val)) { |
|
||||||
this.$emit('input', val) |
|
||||||
} |
|
||||||
}, |
|
||||||
}, |
|
||||||
parentListeners() { |
|
||||||
const $listeners = {} |
|
||||||
|
|
||||||
if (this.$listeners.blur) { |
|
||||||
$listeners.blur = this.$listeners.blur |
|
||||||
} |
|
||||||
if (this.$listeners.focus) { |
|
||||||
$listeners.focus = this.$listeners.focus |
|
||||||
} |
|
||||||
|
|
||||||
if (this.$listeners.cancel) { |
|
||||||
$listeners.cancel = this.$listeners.cancel |
|
||||||
} |
|
||||||
|
|
||||||
return $listeners |
|
||||||
}, |
|
||||||
}, |
|
||||||
mounted() { |
|
||||||
this.$el.focus() |
|
||||||
}, |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<input v-model="localState" v-on="parentListeners" /> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped> |
|
||||||
input, |
|
||||||
textarea { |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
color: var(--v-textColor-base); |
|
||||||
} |
|
||||||
</style> |
|
||||||
<!-- |
|
||||||
/** |
|
||||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
|
||||||
* |
|
||||||
* @author Naveen MR <oof1lab@gmail.com> |
|
||||||
* @author Pranav C Balan <pranavxc@gmail.com> |
|
||||||
* |
|
||||||
* @license GNU AGPL version 3 or any later version |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU Affero General Public License as |
|
||||||
* published by the Free Software Foundation, either version 3 of the |
|
||||||
* License, or (at your option) any later version. |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU Affero General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU Affero General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
* |
|
||||||
*/ |
|
||||||
--> |
|
@ -1,101 +0,0 @@ |
|||||||
<script> |
|
||||||
|
|
||||||
|
|
||||||
import { computed } from "@vue/reactivity"; |
|
||||||
import { ColumnType } from "nocodb-sdk"; |
|
||||||
import { inject, Ref } from "vue"; |
|
||||||
import {enumColor}from "~/utils/colorsUtils"; |
|
||||||
|
|
||||||
const column = inject<ColumnType>("column"); |
|
||||||
const isForm = inject<boolean>("isForm"); |
|
||||||
|
|
||||||
const { modelValue } = defineProps<{ modelValue: any }>(); |
|
||||||
const emit = defineEmits(["update:modelValue"]); |
|
||||||
|
|
||||||
const localState = computed({ |
|
||||||
get() { |
|
||||||
return modelValue?.replace(/\\'/g, "'").replace(/^'|'$/g, ""); |
|
||||||
}, |
|
||||||
set(val) { |
|
||||||
emit("update:modelValue", val); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
const options = computed<string[]>(() => { |
|
||||||
return column?.dtxp?.split(",").map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, "")) || []; |
|
||||||
}); |
|
||||||
|
|
||||||
/*import colors from '@/components/project/spreadsheet/helpers/colors' |
|
||||||
|
|
||||||
export default { |
|
||||||
name: 'SetListCheckboxCell', |
|
||||||
props: { |
|
||||||
value: String, |
|
||||||
column: Object, |
|
||||||
values: Array, |
|
||||||
}, |
|
||||||
data() {}, |
|
||||||
computed: { |
|
||||||
colors() { |
|
||||||
return this.$store.state.settings.darkTheme ? colors.dark : colors.light |
|
||||||
}, |
|
||||||
localState: { |
|
||||||
get() { |
|
||||||
return (this.value && this.value.split(',')) || [] |
|
||||||
}, |
|
||||||
set(val) { |
|
||||||
this.$emit('input', val.join(',')) |
|
||||||
this.$emit('update') |
|
||||||
}, |
|
||||||
}, |
|
||||||
setValues() { |
|
||||||
if (this.column && this.column.dtxp) { |
|
||||||
return this.column.dtxp.split(',').map((v) => v.replace(/^'|'$/g, '')) |
|
||||||
} |
|
||||||
return this.values || [] |
|
||||||
}, |
|
||||||
parentListeners() { |
|
||||||
const $listeners = {} |
|
||||||
|
|
||||||
if (this.$listeners.blur) { |
|
||||||
$listeners.blur = this.$listeners.blur |
|
||||||
} |
|
||||||
if (this.$listeners.focus) { |
|
||||||
$listeners.focus = this.$listeners.focus |
|
||||||
} |
|
||||||
|
|
||||||
return $listeners |
|
||||||
}, |
|
||||||
}, |
|
||||||
mounted() { |
|
||||||
this.$el.focus() |
|
||||||
const event = document.createEvent('MouseEvents') |
|
||||||
event.initMouseEvent('mousedown', true, true, window) |
|
||||||
this.$el.dispatchEvent(event) |
|
||||||
}, |
|
||||||
}*/ |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div class="d-flex align-center"> |
|
||||||
<div> |
|
||||||
<div v-for="(val, i) of setValues" :key="val" class=""> |
|
||||||
<input :id="`key-check-box-${val}`" v-model="localState" type="checkbox" class="orange--text" :value="val" /> |
|
||||||
<label |
|
||||||
class="py-1 px-3 d-inline-block my-1 label" |
|
||||||
:for="`key-check-box-${val}`" |
|
||||||
:style="{ |
|
||||||
background: colors[i % colors.length], |
|
||||||
}" |
|
||||||
>{{ val }}</label |
|
||||||
> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped> |
|
||||||
.label { |
|
||||||
border-radius: 25px; |
|
||||||
} |
|
||||||
</style> |
|
@ -1,122 +0,0 @@ |
|||||||
<script> |
|
||||||
import colors from '@/mixins/colors' |
|
||||||
|
|
||||||
export default { |
|
||||||
name: 'SetListEditableCell', |
|
||||||
mixins: [colors], |
|
||||||
props: { |
|
||||||
value: String, |
|
||||||
column: Object, |
|
||||||
}, |
|
||||||
computed: { |
|
||||||
localState: { |
|
||||||
get() { |
|
||||||
return this.value && this.value.match(/(?:[^',]|\\')+(?='?(?:,|$))/g).map((v) => v.replace(/\\'/g, "'")) |
|
||||||
}, |
|
||||||
set(val) { |
|
||||||
this.$emit('input', val.filter((v) => this.setValues.includes(v)).join(',')) |
|
||||||
}, |
|
||||||
}, |
|
||||||
setValues() { |
|
||||||
if (this.column && this.column.dtxp) { |
|
||||||
return this.column.dtxp.match(/(?:[^']|\\')+(?='?(?:,|$))/g).map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) |
|
||||||
} |
|
||||||
return [] |
|
||||||
}, |
|
||||||
parentListeners() { |
|
||||||
const $listeners = {} |
|
||||||
|
|
||||||
if (this.$listeners.blur) { |
|
||||||
$listeners.blur = this.$listeners.blur |
|
||||||
} |
|
||||||
if (this.$listeners.focus) { |
|
||||||
$listeners.focus = this.$listeners.focus |
|
||||||
} |
|
||||||
|
|
||||||
return $listeners |
|
||||||
}, |
|
||||||
}, |
|
||||||
mounted() { |
|
||||||
// this.$el.focus(); |
|
||||||
// let event; |
|
||||||
// event = document.createEvent('MouseEvents'); |
|
||||||
// event.initMouseEvent('mousedown', true, true, window); |
|
||||||
// this.$el.dispatchEvent(event); |
|
||||||
}, |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<v-combobox |
|
||||||
v-model="localState" |
|
||||||
:items="setValues" |
|
||||||
multiple |
|
||||||
chips |
|
||||||
flat |
|
||||||
dense |
|
||||||
solo |
|
||||||
hide-details |
|
||||||
deletable-chips |
|
||||||
class="text-center mt-0" |
|
||||||
> |
|
||||||
<template #selection="data"> |
|
||||||
<v-chip |
|
||||||
:key="data.item" |
|
||||||
small |
|
||||||
class="ma-1" |
|
||||||
:color="colors[setValues.indexOf(data.item) % colors.length]" |
|
||||||
@click:close="data.parent.selectItem(data.item)" |
|
||||||
> |
|
||||||
{{ data.item }} |
|
||||||
</v-chip> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template #item="{ item }"> |
|
||||||
<v-chip small :color="colors[setValues.indexOf(item) % colors.length]"> |
|
||||||
{{ item }} |
|
||||||
</v-chip> |
|
||||||
</template> |
|
||||||
<template #append> |
|
||||||
<v-icon small class="mt-2"> mdi-menu-down </v-icon> |
|
||||||
</template> |
|
||||||
</v-combobox> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped> |
|
||||||
select { |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
color: var(--v-textColor-base); |
|
||||||
-webkit-appearance: menulist; |
|
||||||
/*webkit browsers */ |
|
||||||
-moz-appearance: menulist; |
|
||||||
/*Firefox */ |
|
||||||
appearance: menulist; |
|
||||||
} |
|
||||||
</style> |
|
||||||
<!-- |
|
||||||
/** |
|
||||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
|
||||||
* |
|
||||||
* @author Naveen MR <oof1lab@gmail.com> |
|
||||||
* @author Pranav C Balan <pranavxc@gmail.com> |
|
||||||
* |
|
||||||
* @license GNU AGPL version 3 or any later version |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU Affero General Public License as |
|
||||||
* published by the Free Software Foundation, either version 3 of the |
|
||||||
* License, or (at your option) any later version. |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU Affero General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU Affero General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
* |
|
||||||
*/ |
|
||||||
--> |
|
@ -1,110 +0,0 @@ |
|||||||
<script> |
|
||||||
import dayjs from 'dayjs' |
|
||||||
|
|
||||||
export default { |
|
||||||
name: 'TimePickerCell', |
|
||||||
props: { |
|
||||||
value: [String, Date], |
|
||||||
}, |
|
||||||
computed: { |
|
||||||
isMysql() { |
|
||||||
return ['mysql', 'mysql2'].indexOf(this.$store.getters['project/GtrClientType']) |
|
||||||
}, |
|
||||||
localState: { |
|
||||||
get() { |
|
||||||
if (!this.value) { |
|
||||||
return this.value |
|
||||||
} |
|
||||||
let dateTime = dayjs(this.value) |
|
||||||
if (!dateTime.isValid()) { |
|
||||||
dateTime = dayjs(this.value, 'HH:mm:ss') |
|
||||||
} |
|
||||||
if (!dateTime.isValid()) { |
|
||||||
dateTime = dayjs(`1999-01-01 ${this.value}`) |
|
||||||
} |
|
||||||
if (!dateTime.isValid()) { |
|
||||||
return this.value |
|
||||||
} |
|
||||||
return dateTime.format('HH:mm:ss') |
|
||||||
}, |
|
||||||
set(val) { |
|
||||||
const dateTime = dayjs(`1999-01-01 ${val}:00`) |
|
||||||
if (dateTime.isValid()) { |
|
||||||
if (this.isMysql) { |
|
||||||
this.$emit('input', dateTime.format('YYYY-MM-DD HH:mm:ss')) |
|
||||||
} else { |
|
||||||
this.$emit('input', dateTime.format('YYYY-MM-DD HH:mm:ssZ')) |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
}, |
|
||||||
parentListeners() { |
|
||||||
const $listeners = {} |
|
||||||
|
|
||||||
if (this.$listeners.blur) { |
|
||||||
$listeners.blur = this.$listeners.blur |
|
||||||
} |
|
||||||
if (this.$listeners.focus) { |
|
||||||
$listeners.focus = this.$listeners.focus |
|
||||||
} |
|
||||||
|
|
||||||
if (this.$listeners.cancel) { |
|
||||||
$listeners.cancel = this.$listeners.cancel |
|
||||||
} |
|
||||||
|
|
||||||
return $listeners |
|
||||||
}, |
|
||||||
}, |
|
||||||
mounted() { |
|
||||||
if (this.$el && this.$el.$el) { |
|
||||||
this.$el.$el.focus() |
|
||||||
} |
|
||||||
}, |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<v-menu> |
|
||||||
<template #activator="{ on }"> |
|
||||||
<input v-model="localState" class="value" v-on="on" /> |
|
||||||
</template> |
|
||||||
<div class="d-flex flex-column justify-center" @click.stop> |
|
||||||
<v-time-picker v-model="localState" v-on="parentListeners" /> |
|
||||||
<v-btn small color="primary" @click="$emit('save')"> |
|
||||||
<!-- Save --> |
|
||||||
{{ $t('general.save') }} |
|
||||||
</v-btn> |
|
||||||
</div> |
|
||||||
</v-menu> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped> |
|
||||||
.value { |
|
||||||
width: 100%; |
|
||||||
min-height: 20px; |
|
||||||
} |
|
||||||
</style> |
|
||||||
<!-- |
|
||||||
/** |
|
||||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
|
||||||
* |
|
||||||
* @author Naveen MR <oof1lab@gmail.com> |
|
||||||
* @author Pranav C Balan <pranavxc@gmail.com> |
|
||||||
* |
|
||||||
* @license GNU AGPL version 3 or any later version |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU Affero General Public License as |
|
||||||
* published by the Free Software Foundation, either version 3 of the |
|
||||||
* License, or (at your option) any later version. |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU Affero General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU Affero General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
* |
|
||||||
*/ |
|
||||||
--> |
|
@ -1,216 +0,0 @@ |
|||||||
<script setup lang="ts"> |
|
||||||
import { computed } from '@vue/reactivity' |
|
||||||
import type { ColumnType } from 'nocodb-sdk' |
|
||||||
import useColumn from '~/composables/useColumn' |
|
||||||
|
|
||||||
const { column, modelValue: value } = defineProps<{ column: ColumnType; modelValue: any }>() |
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']) |
|
||||||
|
|
||||||
provide('column', column) |
|
||||||
|
|
||||||
const localState = computed({ |
|
||||||
get() { |
|
||||||
return value |
|
||||||
}, |
|
||||||
set(val) { |
|
||||||
emit('update:modelValue', val) |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
const { |
|
||||||
isURL, |
|
||||||
isEmail, |
|
||||||
isJSON, |
|
||||||
isDate, |
|
||||||
isDateTime, |
|
||||||
isTime, |
|
||||||
isBoolean, |
|
||||||
isDuration, |
|
||||||
isRating, |
|
||||||
isCurrency, |
|
||||||
isAttachment, |
|
||||||
isTextArea, |
|
||||||
isString, |
|
||||||
isSingleSelect, |
|
||||||
isMultiSelect |
|
||||||
} = useColumn(column) |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div class="nc-cell" @keydown.stop.left @keydown.stop.right @keydown.stop.up @keydown.stop.down> |
|
||||||
<!-- <RatingCell --> |
|
||||||
<!-- v-if="isRating" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- v-model="localState" |
|
||||||
:active="active" |
|
||||||
:is-form="isForm" |
|
||||||
:column="column" |
|
||||||
:is-public-grid="isPublic && !isForm" |
|
||||||
:is-public-form="isPublic && isForm" |
|
||||||
:is-locked="isLocked" |
|
||||||
v-on="$listeners" |
|
||||||
/> --> |
|
||||||
|
|
||||||
<!-- <DurationCell --> |
|
||||||
<!-- v-else-if="isDuration" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- :active="active" --> |
|
||||||
<!-- :is-form="isForm" --> |
|
||||||
<!-- :column="column" --> |
|
||||||
<!-- :is-locked="isLocked" --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- />–> --> |
|
||||||
|
|
||||||
<!-- <IntegerCell --> |
|
||||||
<!-- v-else-if="isInt" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- />–> --> |
|
||||||
|
|
||||||
<!-- <FloatCell --> |
|
||||||
<!-- v-else-if="isFloat" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- />–> --> |
|
||||||
|
|
||||||
<!-- <DatePickerCell --> |
|
||||||
<!-- v-else-if="isDate" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- />–> --> |
|
||||||
|
|
||||||
<!-- <TimePickerCell --> |
|
||||||
<!-- v-else-if="isTime" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- @save="$emit('save')" --> |
|
||||||
<!-- />–> --> |
|
||||||
|
|
||||||
<!-- <DateTimePickerCell --> |
|
||||||
<!-- v-else-if="isDateTime" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- ignore-focus --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- />–> --> |
|
||||||
|
|
||||||
<!-- <EnumCell --> |
|
||||||
<!-- v-else-if="isEnum && ((!isForm && !active) || isLocked || (isPublic && !isForm))" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- :column="column" --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- />–> --> |
|
||||||
<!-- <EnumListCell --> |
|
||||||
<!-- v-else-if="isEnum" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState"–> --> |
|
||||||
<!-- <!– :is-form="isForm"–> --> |
|
||||||
<!-- <!– :column="column"–> --> |
|
||||||
<!-- <!– v-on="parentListeners"–> --> |
|
||||||
<!-- <!– />–> --> |
|
||||||
|
|
||||||
<!-- <JsonEditableCell --> |
|
||||||
<!-- v-else-if="isJSON" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- :is-form="isForm" --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- @input="$emit('save')" --> |
|
||||||
<!-- />–> --> |
|
||||||
|
|
||||||
<!-- <SetListEditableCell --> |
|
||||||
<!-- v-else-if="isSet && (active || isForm) && !isLocked && !(isPublic && !isForm)" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- :column="column" --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- />–> --> |
|
||||||
<!-- <SetListCell --> |
|
||||||
<!-- v-else-if="isSet" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- :column="column" --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- />–> --> |
|
||||||
|
|
||||||
<!-- <EditableUrlCell v-else-if="isURL" --> |
|
||||||
<!-- /> --> |
|
||||||
<!-- <!– v-model="localState" v-on="parentListeners" –> --> |
|
||||||
<!-- <!– />–> --> |
|
||||||
|
|
||||||
<EditableCellText v-if="isString" v-model="localState" /> |
|
||||||
<!-- v-on="parentListeners" |
|
||||||
/> |
|
||||||
--> |
|
||||||
|
|
||||||
<EditableCellTextArea v-else-if="isTextArea" v-model="localState" /> |
|
||||||
<!-- v-model="localState" |
|
||||||
:is-form="isForm" |
|
||||||
v-on="parentListeners" |
|
||||||
/> --> |
|
||||||
|
|
||||||
<EditableCellBoolean v-else-if="isBoolean" v-model="localState" /> |
|
||||||
<!-- <!– v-model="localState" --> |
|
||||||
<!-- :column="column" --> |
|
||||||
<!-- :is-form="isForm" --> |
|
||||||
<!-- v-on="parentListeners" --> |
|
||||||
<!-- />–> --> |
|
||||||
|
|
||||||
<EditableCellAttachment v-if="isAttachment" v-model="localState" /> |
|
||||||
<EditableCellSingleSelect v-if="isSingleSelect" v-model="localState" /> |
|
||||||
<!-- v-model="localState" |
|
||||||
:active="active" |
|
||||||
:db-alias="dbAlias" |
|
||||||
:meta="meta" |
|
||||||
:is-form="isForm" |
|
||||||
:column="column" |
|
||||||
:is-public-grid="isPublic && !isForm" |
|
||||||
:is-public-form="isPublic && isForm" |
|
||||||
:view-id="viewId" |
|
||||||
:is-locked="isLocked" |
|
||||||
v-on="$listeners" |
|
||||||
/> --> |
|
||||||
|
|
||||||
<EditableCellText v-else v-model="localState" /> |
|
||||||
<!-- v-on="$listeners" <span v-if="hint" class="nc-hint">{{ hint }}</span> --> |
|
||||||
|
|
||||||
<!-- <div v-if="(isLocked || (isPublic && !isForm)) && !isAttachment" class="nc-locked-overlay" /> --> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped> |
|
||||||
textarea { |
|
||||||
outline: none; |
|
||||||
} |
|
||||||
|
|
||||||
div { |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
color: var(--v-textColor-base); |
|
||||||
} |
|
||||||
|
|
||||||
.nc-hint { |
|
||||||
font-size: 0.61rem; |
|
||||||
color: grey; |
|
||||||
} |
|
||||||
|
|
||||||
.nc-cell { |
|
||||||
position: relative; |
|
||||||
} |
|
||||||
|
|
||||||
.nc-locked-overlay { |
|
||||||
position: absolute; |
|
||||||
z-index: 2; |
|
||||||
height: 100%; |
|
||||||
width: 100%; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
} |
|
||||||
</style> |
|
@ -0,0 +1,223 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import type { ColumnType } from 'nocodb-sdk' |
||||||
|
import useVirtualCell from '~/composables/useVirtualCell' |
||||||
|
|
||||||
|
const { column, modelValue: value } = defineProps<{ column: ColumnType; modelValue: any; editEnabled: boolean }>() |
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']) |
||||||
|
|
||||||
|
provide('column', column) |
||||||
|
provide('value', value) |
||||||
|
|
||||||
|
const { isLookup, isBt, isRollup, isMm, isHm, isFormula } = useVirtualCell(column) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div class="nc-virtual-cell"> |
||||||
|
<VirtualCellHasMany v-if="isHm" /> |
||||||
|
<VirtualCellManyToMany v-else-if="isMm" /> |
||||||
|
<VirtualCellBelongsTo v-else-if="isBt" /> |
||||||
|
<VirtualCellRollup v-else-if="isRollup" /> |
||||||
|
<VirtualCellRollup v-else-if="isRollup" /> |
||||||
|
|
||||||
|
<!-- <v-lazy> --> |
||||||
|
<!-- <has-many-cell |
||||||
|
v-if="hm" |
||||||
|
ref="cell" |
||||||
|
:row="row" |
||||||
|
:value="row[column.title]" |
||||||
|
:meta="meta" |
||||||
|
:hm="hm" |
||||||
|
:nodes="nodes" |
||||||
|
:active="active" |
||||||
|
:sql-ui="sqlUi" |
||||||
|
:is-new="isNew" |
||||||
|
:is-form="isForm" |
||||||
|
:breadcrumbs="breadcrumbs" |
||||||
|
:is-locked="isLocked" |
||||||
|
:required="required" |
||||||
|
:is-public="isPublic" |
||||||
|
:metas="metas" |
||||||
|
:column="column" |
||||||
|
:password="password" |
||||||
|
v-on="$listeners" |
||||||
|
/> |
||||||
|
<many-to-many-cell |
||||||
|
v-else-if="mm" |
||||||
|
ref="cell" |
||||||
|
:is-public="isPublic" |
||||||
|
:row="row" |
||||||
|
:value="row[column.title]" |
||||||
|
:meta="meta" |
||||||
|
:nodes="nodes" |
||||||
|
:sql-ui="sqlUi" |
||||||
|
:active="active" |
||||||
|
:is-new="isNew" |
||||||
|
:api="api" |
||||||
|
:is-form="isForm" |
||||||
|
:breadcrumbs="breadcrumbs" |
||||||
|
:is-locked="isLocked" |
||||||
|
:required="required" |
||||||
|
:column="column" |
||||||
|
:metas="metas" |
||||||
|
:password="password" |
||||||
|
v-on="$listeners" |
||||||
|
/> |
||||||
|
<belongs-to-cell |
||||||
|
v-else-if="bt" |
||||||
|
ref="cell" |
||||||
|
:is-public="isPublic" |
||||||
|
:disabled-columns="disabledColumns" |
||||||
|
:active="active" |
||||||
|
:row="row" |
||||||
|
:value="row[column.title]" |
||||||
|
:meta="meta" |
||||||
|
:nodes="nodes" |
||||||
|
:api="api" |
||||||
|
:sql-ui="sqlUi" |
||||||
|
:is-new="isNew" |
||||||
|
:is-form="isForm" |
||||||
|
:breadcrumbs="breadcrumbs" |
||||||
|
:is-locked="isLocked" |
||||||
|
:metas="metas" |
||||||
|
:column="column" |
||||||
|
:password="password" |
||||||
|
v-on="$listeners" |
||||||
|
/> |
||||||
|
<lookup-cell |
||||||
|
v-else-if="lookup" |
||||||
|
:disabled-columns="disabledColumns" |
||||||
|
:active="active" |
||||||
|
:row="row" |
||||||
|
:value="row[column.title]" |
||||||
|
:meta="meta" |
||||||
|
:metas="metas" |
||||||
|
:nodes="nodes" |
||||||
|
:api="api" |
||||||
|
:sql-ui="sqlUi" |
||||||
|
:is-new="isNew" |
||||||
|
:is-form="isForm" |
||||||
|
:column="column" |
||||||
|
:is-locked="isLocked" |
||||||
|
v-on="$listeners " |
||||||
|
/> |
||||||
|
<formula-cell |
||||||
|
v-else-if="formula" |
||||||
|
:row="row" |
||||||
|
:column="column" |
||||||
|
:client="nodes.dbConnection.client" |
||||||
|
/> |
||||||
|
<rollup-cell |
||||||
|
v-else-if="rollup" |
||||||
|
:row="row" |
||||||
|
:column="column" |
||||||
|
/> |
||||||
|
</v-lazy> |
||||||
|
<span v-if="hint" class="nc-hint">{{ hint }}</span> |
||||||
|
<div v-if="isLocked" class="nc-locked-overlay" /> --> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<!-- <script> |
||||||
|
import { UITypes } from "nocodb-sdk"; |
||||||
|
import RollupCell from "./virtualCell/RollupCell"; |
||||||
|
import FormulaCell from "./virtualCell/FormulaCell"; |
||||||
|
import hasManyCell from "./virtualCell/HasManyCell"; |
||||||
|
import LookupCell from "./virtualCell/LookupCell"; |
||||||
|
import manyToManyCell from "./virtualCell/ManyToManyCell"; |
||||||
|
import belongsToCell from "./virtualCell/BelongsToCell"; |
||||||
|
|
||||||
|
// todo: optimize parent/child meta extraction |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "VirtualCell", |
||||||
|
components: { |
||||||
|
RollupCell, |
||||||
|
FormulaCell, |
||||||
|
LookupCell, |
||||||
|
belongsToCell, |
||||||
|
manyToManyCell, |
||||||
|
hasManyCell |
||||||
|
}, |
||||||
|
props: { |
||||||
|
breadcrumbs: { |
||||||
|
type: Array, |
||||||
|
default() { |
||||||
|
return []; |
||||||
|
} |
||||||
|
}, |
||||||
|
column: [Object], |
||||||
|
row: [Object], |
||||||
|
nodes: [Object], |
||||||
|
meta: [Object], |
||||||
|
api: [Object, Function], |
||||||
|
active: Boolean, |
||||||
|
sqlUi: [Object, Function], |
||||||
|
isNew: { |
||||||
|
type: Boolean, |
||||||
|
default: false |
||||||
|
}, |
||||||
|
isForm: { |
||||||
|
type: Boolean, |
||||||
|
default: false |
||||||
|
}, |
||||||
|
disabledColumns: Object, |
||||||
|
hint: String, |
||||||
|
isLocked: Boolean, |
||||||
|
required: Boolean, |
||||||
|
isPublic: Boolean, |
||||||
|
metas: Object, |
||||||
|
password: String |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
hm() { |
||||||
|
return this.column && this.column.uidt === UITypes.LinkToAnotherRecord && this.column.colOptions.type === "hm"; |
||||||
|
}, |
||||||
|
bt() { |
||||||
|
return this.column && (this.column.uidt === UITypes.ForeignKey || this.column.uidt === UITypes.LinkToAnotherRecord) && this.column.colOptions.type === "bt"; |
||||||
|
}, |
||||||
|
mm() { |
||||||
|
return this.column && this.column.uidt === UITypes.LinkToAnotherRecord && this.column.colOptions.type === "mm"; |
||||||
|
}, |
||||||
|
lookup() { |
||||||
|
return this.column && this.column.uidt === UITypes.Lookup; |
||||||
|
}, |
||||||
|
formula() { |
||||||
|
return this.column && this.column.uidt === UITypes.Formula; |
||||||
|
}, |
||||||
|
rollup() { |
||||||
|
return this.column && this.column.uidt === UITypes.Rollup; |
||||||
|
} |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
async save(row) { |
||||||
|
if (row && this.$refs.cell && this.$refs.cell.saveLocalState) { |
||||||
|
try { |
||||||
|
await this.$refs.cell.saveLocalState(row); |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> --> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.nc-hint { |
||||||
|
font-size: 0.61rem; |
||||||
|
color: grey; |
||||||
|
} |
||||||
|
|
||||||
|
.nc-virtual-cell { |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.nc-locked-overlay { |
||||||
|
position: absolute; |
||||||
|
z-index: 2; |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,493 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
|
||||||
|
import { ColumnType } from "nocodb-sdk"; |
||||||
|
import useBelongsTo from "~/composables/useBelongsTo"; |
||||||
|
import ItemChip from "./components/ItemChip.vue"; |
||||||
|
|
||||||
|
const column = inject<ColumnType>("column"); |
||||||
|
const value = inject("value"); |
||||||
|
|
||||||
|
const { parentMeta, loadParentMeta, primaryValueProp } = useBelongsTo(column as ColumnType); |
||||||
|
await loadParentMeta(); |
||||||
|
// import ApiFactory from '@/components/project/spreadsheet/apis/apiFactory' |
||||||
|
/*import { RelationTypes, UITypes, isSystemColumn } from 'nocodb-sdk' |
||||||
|
import ListItems from '~/components/project/spreadsheet/components/virtualCell/components/ListItems' |
||||||
|
import ListChildItems from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItems' |
||||||
|
import ItemChip from '~/components/project/spreadsheet/components/virtualCell/components/ItemChip' |
||||||
|
import { parseIfInteger } from '@/helpers' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'BelongsToCell', |
||||||
|
components: { ListChildItems, ItemChip, ListItems }, |
||||||
|
props: { |
||||||
|
isLocked: Boolean, |
||||||
|
breadcrumbs: { |
||||||
|
type: Array, |
||||||
|
default() { |
||||||
|
return [] |
||||||
|
}, |
||||||
|
}, |
||||||
|
isForm: Boolean, |
||||||
|
value: [Array, Object], |
||||||
|
meta: [Object], |
||||||
|
nodes: [Object], |
||||||
|
row: [Object], |
||||||
|
api: [Object, Function], |
||||||
|
sqlUi: [Object, Function], |
||||||
|
active: Boolean, |
||||||
|
isNew: Boolean, |
||||||
|
disabledColumns: Object, |
||||||
|
isPublic: Boolean, |
||||||
|
metas: Object, |
||||||
|
password: String, |
||||||
|
column: Object, |
||||||
|
}, |
||||||
|
data: () => ({ |
||||||
|
newRecordModal: false, |
||||||
|
parentListModal: false, |
||||||
|
// parentMeta: null, |
||||||
|
list: null, |
||||||
|
childList: null, |
||||||
|
dialogShow: false, |
||||||
|
confirmAction: null, |
||||||
|
confirmMessage: '', |
||||||
|
selectedParent: null, |
||||||
|
isNewParent: false, |
||||||
|
expandFormModal: false, |
||||||
|
localState: null, |
||||||
|
pid: null, |
||||||
|
}), |
||||||
|
computed: { |
||||||
|
parentMeta() { |
||||||
|
return this.metas |
||||||
|
? this.metas[this.column.colOptions.fk_related_model_id] |
||||||
|
: this.$store.state.meta.metas[this.column.colOptions.fk_related_model_id] |
||||||
|
}, |
||||||
|
// todo : optimize |
||||||
|
parentApi() {}, |
||||||
|
parentId() { |
||||||
|
return ( |
||||||
|
this.pid ?? |
||||||
|
(this.value && |
||||||
|
this.parentMeta && |
||||||
|
this.parentMeta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => this.value[c.title]) |
||||||
|
.join('___')) |
||||||
|
) |
||||||
|
}, |
||||||
|
rowId() { |
||||||
|
return ( |
||||||
|
this.row && |
||||||
|
this.meta && |
||||||
|
this.meta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => this.row[c.title]) |
||||||
|
.join('___') |
||||||
|
) |
||||||
|
}, |
||||||
|
parentPrimaryCol() { |
||||||
|
return this.parentMeta && (this.parentMeta.columns.find((c) => c.pv) || {}).title |
||||||
|
}, |
||||||
|
parentPrimaryKey() { |
||||||
|
return this.parentMeta && (this.parentMeta.columns.find((c) => c.pk) || {}).title |
||||||
|
}, |
||||||
|
parentReferenceKey() { |
||||||
|
return ( |
||||||
|
this.parentMeta && (this.parentMeta.columns.find((c) => c.id === this.column.colOptions.fk_parent_column_id) || {}).title |
||||||
|
) |
||||||
|
}, |
||||||
|
btWhereClause() { |
||||||
|
// if parent reference key is pk, then filter out the selected value |
||||||
|
// else, filter out the selected value + empty values (as we can't set an empty value) |
||||||
|
const prk = this.parentReferenceKey |
||||||
|
const selectedValue = |
||||||
|
this.meta && this.meta.columns |
||||||
|
? this.meta.columns |
||||||
|
.filter((c) => c.id === this.column.colOptions.fk_child_column_id) |
||||||
|
.map((c) => this.row[c.title] || '') |
||||||
|
.join('___') |
||||||
|
: '' |
||||||
|
return `(${prk},not,${selectedValue})~or(${prk},is,null)` |
||||||
|
}, |
||||||
|
parentQueryParams() { |
||||||
|
if (!this.parentMeta) { |
||||||
|
return {} |
||||||
|
} |
||||||
|
// todo: use reduce |
||||||
|
return {} |
||||||
|
}, |
||||||
|
parentAvailableColumns() { |
||||||
|
if (!this.parentMeta) { |
||||||
|
return [] |
||||||
|
} |
||||||
|
|
||||||
|
const columns = [] |
||||||
|
if (this.parentMeta.columns) { |
||||||
|
columns.push(...this.parentMeta.columns.filter((c) => !isSystemColumn(c))) |
||||||
|
} |
||||||
|
return columns |
||||||
|
}, |
||||||
|
// todo: |
||||||
|
form() { |
||||||
|
return this.selectedParent && !this.isPublic |
||||||
|
? () => import('~/components/project/spreadsheet/components/ExpandedForm') |
||||||
|
: 'span' |
||||||
|
}, |
||||||
|
cellValue() { |
||||||
|
if (this.value || this.localState) { |
||||||
|
if (this.parentMeta && this.parentPrimaryCol) { |
||||||
|
return (this.value || this.localState)[this.parentPrimaryCol] |
||||||
|
} |
||||||
|
return Object.values(this.value || this.localState)[1] |
||||||
|
} |
||||||
|
return null |
||||||
|
}, |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
isNew(n, o) { |
||||||
|
if (!n && o) { |
||||||
|
this.localState = null |
||||||
|
this.$emit('update:localState', this.localState) |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
async mounted() { |
||||||
|
if (this.isNew && this.value) { |
||||||
|
this.localState = this.value |
||||||
|
} |
||||||
|
if (this.isForm) { |
||||||
|
await this.loadParentMeta() |
||||||
|
} |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.loadParentMeta() |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
async onParentSave(parent) { |
||||||
|
if (this.isNewParent) { |
||||||
|
await this.addChildToParent(parent) |
||||||
|
} else { |
||||||
|
this.$emit('loadTableData') |
||||||
|
} |
||||||
|
}, |
||||||
|
async insertAndMapNewParentRecord() { |
||||||
|
await this.loadParentMeta() |
||||||
|
this.newRecordModal = false |
||||||
|
this.isNewParent = true |
||||||
|
this.selectedParent = { |
||||||
|
[( |
||||||
|
this.parentMeta.columns.find( |
||||||
|
(c) => |
||||||
|
c.uidt === UITypes.LinkToAnotherRecord && |
||||||
|
c.colOptions && |
||||||
|
this.column.colOptions && |
||||||
|
c.colOptions.fk_child_column_id === this.column.colOptions.fk_child_column_id && |
||||||
|
c.colOptions.fk_parent_column_id === this.column.colOptions.fk_parent_column_id && |
||||||
|
c.colOptions.type === RelationTypes.HAS_MANY, |
||||||
|
) || {} |
||||||
|
).title]: [this.row], |
||||||
|
} |
||||||
|
this.expandFormModal = true |
||||||
|
}, |
||||||
|
|
||||||
|
async unlink(parent) { |
||||||
|
const column = this.meta.columns.find((c) => c.id === this.column.colOptions.fk_child_column_id) |
||||||
|
const _cn = column.title |
||||||
|
if (this.isNew) { |
||||||
|
this.$emit('updateCol', this.row, _cn, null) |
||||||
|
this.localState = null |
||||||
|
this.$emit('update:localState', this.localState) |
||||||
|
return |
||||||
|
} |
||||||
|
if (column.rqd) { |
||||||
|
this.$toast.info('Unlink is not possible, instead map to another parent.').goAway(3000) |
||||||
|
return |
||||||
|
} |
||||||
|
const id = this.meta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => this.row[c.title]) |
||||||
|
.join('___') |
||||||
|
|
||||||
|
// todo: audit |
||||||
|
await this.$api.dbTableRow.nestedRemove( |
||||||
|
'noco', |
||||||
|
this.projectName, |
||||||
|
this.meta.title, |
||||||
|
id, |
||||||
|
'bt', |
||||||
|
this.column.title, |
||||||
|
parent[this.parentPrimaryKey], |
||||||
|
) |
||||||
|
|
||||||
|
this.$emit('loadTableData') |
||||||
|
if (this.isForm && this.$refs.childList) { |
||||||
|
this.$refs.childList.loadData() |
||||||
|
} |
||||||
|
}, |
||||||
|
async showParentListModal() { |
||||||
|
this.parentListModal = true |
||||||
|
await this.loadParentMeta() |
||||||
|
const pid = this.meta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => this.row[c.title]) |
||||||
|
.join('___') |
||||||
|
const _cn = this.parentMeta.columns.find((c) => c.column_name === this.hm.column_name).title |
||||||
|
this.childList = await this.parentApi.paginatedList({ |
||||||
|
where: `(${_cn},eq,${pid})`, |
||||||
|
}) |
||||||
|
}, |
||||||
|
async removeChild(child) { |
||||||
|
this.dialogShow = true |
||||||
|
this.confirmMessage = 'Do you want to delete the record?' |
||||||
|
this.confirmAction = async (act) => { |
||||||
|
if (act === 'hideDialog') { |
||||||
|
this.dialogShow = false |
||||||
|
} else { |
||||||
|
const id = this.parentMeta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => child[c.title]) |
||||||
|
.join('___') |
||||||
|
await this.parentApi.delete(id) |
||||||
|
this.pid = null |
||||||
|
this.dialogShow = false |
||||||
|
this.$emit('loadTableData') |
||||||
|
if (this.isForm && this.$refs.childList) { |
||||||
|
this.$refs.childList.loadData() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
async loadParentMeta() { |
||||||
|
// todo: optimize |
||||||
|
if (!this.parentMeta) { |
||||||
|
await this.$store.dispatch('meta/ActLoadMeta', { |
||||||
|
env: this.nodes.env, |
||||||
|
dbAlias: this.nodes.dbAlias, |
||||||
|
id: this.column.colOptions.fk_related_model_id, |
||||||
|
}) |
||||||
|
} |
||||||
|
}, |
||||||
|
async showNewRecordModal() { |
||||||
|
await this.loadParentMeta() |
||||||
|
this.newRecordModal = true |
||||||
|
}, |
||||||
|
async addChildToParent(parent) { |
||||||
|
const pid = this._extractRowId(parent, this.parentMeta) |
||||||
|
const id = this._extractRowId(this.row, this.meta) |
||||||
|
const _cn = this.meta.columns.find((c) => c.id === this.column.colOptions.fk_child_column_id).title |
||||||
|
|
||||||
|
if (this.isNew) { |
||||||
|
const _rcn = this.parentMeta.columns.find((c) => c.id === this.column.colOptions.fk_parent_column_id).title |
||||||
|
this.localState = parent |
||||||
|
this.$emit('update:localState', this.localState) |
||||||
|
this.$emit('updateCol', this.row, _cn, parent[_rcn]) |
||||||
|
this.newRecordModal = false |
||||||
|
return |
||||||
|
} |
||||||
|
await this.$api.dbTableRow.nestedAdd('noco', this.projectName, this.meta.title, id, 'bt', this.column.title, pid) |
||||||
|
|
||||||
|
this.pid = pid |
||||||
|
|
||||||
|
this.newRecordModal = false |
||||||
|
|
||||||
|
this.$emit('loadTableData') |
||||||
|
if (this.isForm && this.$refs.childList) { |
||||||
|
this.$refs.childList.loadData() |
||||||
|
} |
||||||
|
}, |
||||||
|
async editParent(parent) { |
||||||
|
await this.loadParentMeta() |
||||||
|
this.isNewParent = false |
||||||
|
this.selectedParent = parent |
||||||
|
this.expandFormModal = true |
||||||
|
setTimeout(() => { |
||||||
|
this.$refs.expandedForm && this.$refs.expandedForm.reload() |
||||||
|
}, 500) |
||||||
|
}, |
||||||
|
}, |
||||||
|
}*/ |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div class="d-flex d-100 chips-wrapper" :class="{ active }"> |
||||||
|
<!-- <template v-if="!isForm">--> |
||||||
|
<div class="chips d-flex align-center img-container flex-grow-1 hm-items"> |
||||||
|
<template v-if="value || localState"> |
||||||
|
<ItemChip |
||||||
|
:active="active" |
||||||
|
:item="value" |
||||||
|
:value="value[primaryValueProp]" |
||||||
|
|
||||||
|
/> |
||||||
|
<!-- :readonly="isLocked || (isPublic && !isForm)" |
||||||
|
@edit="editParent" |
||||||
|
@unlink="unlink"--> |
||||||
|
</template> |
||||||
|
</div> |
||||||
|
<!-- <div |
||||||
|
v-if="!isLocked && _isUIAllowed('xcDatatableEditable') && (isForm || !isPublic)" |
||||||
|
class="action align-center justify-center px-1 flex-shrink-1" |
||||||
|
:class="{ 'd-none': !active, 'd-flex': active }" |
||||||
|
> |
||||||
|
<x-icon small :color="['primary', 'grey']" @click="showNewRecordModal"> |
||||||
|
{{ value ? 'mdi-arrow-expand' : 'mdi-plus' }} |
||||||
|
</x-icon> |
||||||
|
</div>--> |
||||||
|
<!-- </template>--> |
||||||
|
<!-- <ListItems |
||||||
|
v-if="newRecordModal" |
||||||
|
:key="parentId" |
||||||
|
v-model="newRecordModal" |
||||||
|
:size="10" |
||||||
|
:meta="parentMeta" |
||||||
|
:column="column" |
||||||
|
:primary-col="parentPrimaryCol" |
||||||
|
:primary-key="parentPrimaryKey" |
||||||
|
:parent-meta="meta" |
||||||
|
:api="parentApi" |
||||||
|
:query-params="{ |
||||||
|
...parentQueryParams, |
||||||
|
where: isNew ? null : `${btWhereClause}`, |
||||||
|
}" |
||||||
|
:is-public="isPublic" |
||||||
|
:tn="bt && bt.rtn" |
||||||
|
:password="password" |
||||||
|
:row-id="rowId" |
||||||
|
@add-new-record="insertAndMapNewParentRecord" |
||||||
|
@add="addChildToParent" |
||||||
|
/> |
||||||
|
|
||||||
|
<ListChildItems |
||||||
|
v-if="parentMeta && isForm" |
||||||
|
ref="childList" |
||||||
|
:is-form="isForm" |
||||||
|
:local-state="localState ? [localState] : []" |
||||||
|
:is-new="isNew" |
||||||
|
:size="10" |
||||||
|
:parent-meta="parentMeta" |
||||||
|
:meta="parentMeta" |
||||||
|
:primary-col="parentPrimaryCol" |
||||||
|
:primary-key="parentPrimaryKey" |
||||||
|
:api="parentApi" |
||||||
|
:query-params="{ |
||||||
|
...parentQueryParams, |
||||||
|
where: `(${parentPrimaryKey},eq,${parentId})`, |
||||||
|
}" |
||||||
|
:bt="value" |
||||||
|
:is-public="isPublic" |
||||||
|
:row-id="parentId" |
||||||
|
@new-record="showNewRecordModal" |
||||||
|
@edit="editParent" |
||||||
|
@unlink="unlink" |
||||||
|
/> |
||||||
|
|
||||||
|
<v-dialog |
||||||
|
v-if="!isPublic && selectedParent" |
||||||
|
v-model="expandFormModal" |
||||||
|
:overlay-opacity="0.8" |
||||||
|
width="1000px" |
||||||
|
max-width="100%" |
||||||
|
class="mx-auto" |
||||||
|
> |
||||||
|
<component |
||||||
|
:is="form" |
||||||
|
v-if="selectedParent" |
||||||
|
ref="expandedForm" |
||||||
|
v-model="selectedParent" |
||||||
|
v-model:is-new="isNewParent" |
||||||
|
:db-alias="nodes.dbAlias" |
||||||
|
:has-many="parentMeta.hasMany" |
||||||
|
:belongs-to="parentMeta.belongsTo" |
||||||
|
:table="parentMeta.table_name" |
||||||
|
:old-row="{ ...selectedParent }" |
||||||
|
:meta="parentMeta" |
||||||
|
:sql-ui="sqlUi" |
||||||
|
:primary-value-column="parentPrimaryCol" |
||||||
|
:api="parentApi" |
||||||
|
:available-columns="parentAvailableColumns" |
||||||
|
:nodes="nodes" |
||||||
|
:query-params="parentQueryParams" |
||||||
|
icon-color="warning" |
||||||
|
:breadcrumbs="breadcrumbs" |
||||||
|
@cancel=" |
||||||
|
selectedParent = null |
||||||
|
expandFormModal = false |
||||||
|
" |
||||||
|
@input="onParentSave" |
||||||
|
/> |
||||||
|
</v-dialog>--> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.items-container { |
||||||
|
overflow-x: visible; |
||||||
|
max-height: min(500px, 60vh); |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.primary-value { |
||||||
|
.primary-key { |
||||||
|
display: none; |
||||||
|
margin-left: 0.5em; |
||||||
|
} |
||||||
|
|
||||||
|
&:hover .primary-key { |
||||||
|
display: inline; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.child-card { |
||||||
|
cursor: pointer; |
||||||
|
|
||||||
|
&:hover { |
||||||
|
box-shadow: 0 0 0.2em var(--v-textColor-lighten5); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.hm-items { |
||||||
|
flex-wrap: wrap; |
||||||
|
row-gap: 3px; |
||||||
|
gap: 3px; |
||||||
|
margin: 3px auto; |
||||||
|
} |
||||||
|
|
||||||
|
.chips-wrapper { |
||||||
|
.chips { |
||||||
|
max-width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
&.active { |
||||||
|
.chips { |
||||||
|
max-width: calc(100% - 22px); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
<!-- |
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Naveen MR <oof1lab@gmail.com> |
||||||
|
* @author Pranav C Balan <pranavxc@gmail.com> |
||||||
|
* @author Md Ishtiaque Zafar <ishtiaque.zafar92@gmail.com> |
||||||
|
* @author Wing-Kam Wong <wingkwong.code@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
--> |
@ -0,0 +1,93 @@ |
|||||||
|
<script> |
||||||
|
import dayjs from 'dayjs' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'FormulaCell', |
||||||
|
props: { column: Object, row: Object, client: String }, |
||||||
|
data: () => ({ |
||||||
|
showEditFormulaWarning: false, |
||||||
|
}), |
||||||
|
computed: { |
||||||
|
result() { |
||||||
|
if (this.client === 'pg') { |
||||||
|
return this.handleTZ(this.row[this.column.title]) |
||||||
|
} |
||||||
|
return this.row[this.column.title] |
||||||
|
}, |
||||||
|
urls() { |
||||||
|
if (!this.row[this.column.title]) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
const rawText = this.result.toString() |
||||||
|
let found = false |
||||||
|
const out = rawText.replace(/URI::\((.*?)\)/g, (_, url) => { |
||||||
|
found = true |
||||||
|
const a = document.createElement('a') |
||||||
|
a.textContent = url |
||||||
|
a.setAttribute('href', url) |
||||||
|
a.setAttribute('target', '_blank') |
||||||
|
return a.outerHTML |
||||||
|
}) |
||||||
|
|
||||||
|
return found && out |
||||||
|
}, |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
// handle date returned from PostgreSQL |
||||||
|
handleTZ(val) { |
||||||
|
if (!val) { |
||||||
|
return |
||||||
|
} |
||||||
|
if (typeof val !== 'string') { |
||||||
|
return val |
||||||
|
} |
||||||
|
return val.replace( |
||||||
|
/((?:-?(?:[1-9][0-9]*)?[0-9]{4})-(?:1[0-2]|0[1-9])-(?:3[01]|0[1-9]|[12][0-9])T(?:2[0-3]|[01][0-9]):(?:[0-5][0-9]):(?:[0-5][0-9])(?:\.[0-9]+)?(?:Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9]))/g, |
||||||
|
(i, v) => { |
||||||
|
return dayjs(v).format('YYYY-MM-DD HH:mm') |
||||||
|
}, |
||||||
|
) |
||||||
|
}, |
||||||
|
showEditFormulaWarningMessage() { |
||||||
|
this.showEditFormulaWarning = true |
||||||
|
setTimeout(() => { |
||||||
|
this.showEditFormulaWarning = false |
||||||
|
}, 3000) |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div> |
||||||
|
<v-tooltip v-if="column && column.colOptions && column.colOptions.error" bottom color="error"> |
||||||
|
<template #activator="{ on }"> |
||||||
|
<span class="caption" v-on="on">ERR<span class="error--text">!</span></span> |
||||||
|
</template> |
||||||
|
<span class="font-weight-bold">{{ column.colOptions.error }}</span> |
||||||
|
</v-tooltip> |
||||||
|
<div class="formula-cell-wrapper" @dblclick="showEditFormulaWarningMessage"> |
||||||
|
<div v-if="urls" v-html="urls" /> |
||||||
|
<div v-else> |
||||||
|
{{ result }} |
||||||
|
</div> |
||||||
|
<div v-if="showEditFormulaWarning == true" class="edit-warning"> |
||||||
|
<!-- TODO: i18n --> |
||||||
|
Warning: Formula fields should be configured in the field menu dropdown. |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.formula-cell-wrapper { |
||||||
|
padding: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.edit-warning { |
||||||
|
text-align: left; |
||||||
|
margin-top: 10px; |
||||||
|
color: #e65100; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,603 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import { computed } from '@vue/reactivity' |
||||||
|
import type { ColumnType } from 'nocodb-sdk' |
||||||
|
import ItemChip from './components/ItemChip.vue' |
||||||
|
import useHasMany from '~/composables/useHasMany' |
||||||
|
|
||||||
|
const column = inject<ColumnType>('column') |
||||||
|
const value = inject('value') |
||||||
|
|
||||||
|
const { childMeta, loadChildMeta, primaryValueProp } = useHasMany(column as ColumnType) |
||||||
|
await loadChildMeta() |
||||||
|
|
||||||
|
/* // import ApiFactory from '@/components/project/spreadsheet/apis/apiFactory' |
||||||
|
import { RelationTypes, UITypes, isSystemColumn } from 'nocodb-sdk' |
||||||
|
import DlgLabelSubmitCancel from '~/components/utils/DlgLabelSubmitCancel' |
||||||
|
import Pagination from '~/components/project/spreadsheet/components/Pagination' |
||||||
|
import ListItems from '~/components/project/spreadsheet/components/virtualCell/components/ListItems' |
||||||
|
import ListChildItems from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItems' |
||||||
|
import listChildItemsModal from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItemsModal' |
||||||
|
import { parseIfInteger } from '@/helpers' |
||||||
|
import ItemChip from '~/components/project/spreadsheet/components/virtualCell/components/ItemChip' |
||||||
|
|
||||||
|
// todo: handling add new record for new row |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'HasManyCell', |
||||||
|
components: { |
||||||
|
ListChildItems, |
||||||
|
ItemChip, |
||||||
|
ListItems, |
||||||
|
Pagination, |
||||||
|
DlgLabelSubmitCancel, |
||||||
|
ListChildItemsModal: listChildItemsModal, |
||||||
|
}, |
||||||
|
props: { |
||||||
|
isLocked: Boolean, |
||||||
|
breadcrumbs: { |
||||||
|
type: Array, |
||||||
|
default() { |
||||||
|
return [] |
||||||
|
}, |
||||||
|
}, |
||||||
|
value: [Object, Array], |
||||||
|
meta: [Object], |
||||||
|
nodes: [Object], |
||||||
|
row: [Object], |
||||||
|
active: Boolean, |
||||||
|
isNew: Boolean, |
||||||
|
isForm: Boolean, |
||||||
|
required: Boolean, |
||||||
|
isPublic: Boolean, |
||||||
|
metas: Object, |
||||||
|
password: String, |
||||||
|
column: Object, |
||||||
|
}, |
||||||
|
data: () => ({ |
||||||
|
newRecordModal: false, |
||||||
|
childListModal: false, |
||||||
|
// childMeta: null, |
||||||
|
dialogShow: false, |
||||||
|
confirmAction: null, |
||||||
|
confirmMessage: '', |
||||||
|
selectedChild: null, |
||||||
|
expandFormModal: false, |
||||||
|
isNewChild: false, |
||||||
|
localState: [], |
||||||
|
}), |
||||||
|
computed: { |
||||||
|
childMeta() { |
||||||
|
return this.metas |
||||||
|
? this.metas[this.column.colOptions.fk_related_model_id] |
||||||
|
: this.$store.state.meta.metas[this.column.colOptions.fk_related_model_id] |
||||||
|
}, |
||||||
|
// todo : optimize |
||||||
|
childApi() {}, |
||||||
|
childPrimaryCol() { |
||||||
|
return this.childMeta && (this.childMeta.columns.find((c) => c.pv) || {}).title |
||||||
|
}, |
||||||
|
primaryCol() { |
||||||
|
return this.meta && (this.meta.columns.find((c) => c.pv) || {}).title |
||||||
|
}, |
||||||
|
childPrimaryKey() { |
||||||
|
return this.childMeta && (this.childMeta.columns.find((c) => c.pk) || {}).title |
||||||
|
}, |
||||||
|
childForeignKey() { |
||||||
|
return ( |
||||||
|
this.childMeta && (this.childMeta.columns.find((c) => c.id === this.column.colOptions.fk_child_column_id) || {}).title |
||||||
|
) |
||||||
|
}, |
||||||
|
childForeignKeyVal() { |
||||||
|
return this.meta && this.meta.columns |
||||||
|
? this.meta.columns |
||||||
|
.filter((c) => c.title === this.childForeignKey) |
||||||
|
.map((c) => this.row[c.title] || '') |
||||||
|
.join('___') |
||||||
|
: '' |
||||||
|
}, |
||||||
|
isVirtualRelation() { |
||||||
|
return this.column && this.column.colOptions.virtual // (this.childMeta && (!!this.childMeta.columns.find(c => c.column_name === this.hm.column_name && this.hm.type === 'virtual'))) || false |
||||||
|
}, |
||||||
|
isByPass() { |
||||||
|
if (this.isVirtualRelation) { |
||||||
|
return false |
||||||
|
} |
||||||
|
// if child fk references a column in parent which is not pk, |
||||||
|
// then this column has to be filled |
||||||
|
// if (((this.meta && this.meta.columns.find(c => !c.pk && c.id === this.hm.rcn)) || false)) { |
||||||
|
// return this.childForeignKeyVal === '' |
||||||
|
// } |
||||||
|
if ((this.meta && this.meta.columns.find((c) => !c.pk && c.id === this.column.fk_parent_column_id)) || false) { |
||||||
|
return this.childForeignKeyVal === '' |
||||||
|
} |
||||||
|
return false |
||||||
|
}, |
||||||
|
disabledChildColumns() { |
||||||
|
return { [this.childForeignKey]: true } |
||||||
|
}, |
||||||
|
// todo: |
||||||
|
form() { |
||||||
|
return this.selectedChild && !this.isPublic |
||||||
|
? () => import('~/components/project/spreadsheet/components/ExpandedForm') |
||||||
|
: 'span' |
||||||
|
}, |
||||||
|
childAvailableColumns() { |
||||||
|
if (!this.childMeta) { |
||||||
|
return [] |
||||||
|
} |
||||||
|
|
||||||
|
const columns = [] |
||||||
|
if (this.childMeta.columns) { |
||||||
|
columns.push(...this.childMeta.columns.filter((c) => !isSystemColumn(c))) |
||||||
|
} |
||||||
|
return columns |
||||||
|
}, |
||||||
|
childQueryParams() { |
||||||
|
if (!this.childMeta) { |
||||||
|
return {} |
||||||
|
} |
||||||
|
// todo: use reduce |
||||||
|
return { |
||||||
|
hm: |
||||||
|
(this.childMeta && |
||||||
|
this.childMeta.v && |
||||||
|
this.childMeta.v |
||||||
|
.filter((v) => v.hm) |
||||||
|
.map(({ hm }) => hm.table_name) |
||||||
|
.join()) || |
||||||
|
'', |
||||||
|
bt: |
||||||
|
(this.childMeta && |
||||||
|
this.childMeta.v && |
||||||
|
this.childMeta.v |
||||||
|
.filter((v) => v.bt) |
||||||
|
.map(({ bt }) => bt.rtn) |
||||||
|
.join()) || |
||||||
|
'', |
||||||
|
mm: |
||||||
|
(this.childMeta && |
||||||
|
this.childMeta.v && |
||||||
|
this.childMeta.v |
||||||
|
.filter((v) => v.mm) |
||||||
|
.map(({ mm }) => mm.rtn) |
||||||
|
.join()) || |
||||||
|
'', |
||||||
|
} |
||||||
|
}, |
||||||
|
parentId() { |
||||||
|
return ( |
||||||
|
(this.meta && |
||||||
|
this.meta.columns && |
||||||
|
(this.meta.columns |
||||||
|
.filter((c) => c.title === this.childForeignKey) |
||||||
|
.map((c) => this.row[c.title] || '') |
||||||
|
.join('___') || |
||||||
|
this.meta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => this.row[c.title]) |
||||||
|
.join('___'))) || |
||||||
|
'' |
||||||
|
) |
||||||
|
}, |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
isNew(n, o) { |
||||||
|
if (!n && o) { |
||||||
|
this.saveLocalState() |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
async mounted() { |
||||||
|
await this.loadChildMeta() |
||||||
|
|
||||||
|
if (this.isNew && this.value) { |
||||||
|
this.localState = [...this.value] |
||||||
|
} |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.loadChildMeta() |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
onChildSave() { |
||||||
|
if (this.isNew) { |
||||||
|
this.addChildToParent(this.selectedChild) |
||||||
|
} else { |
||||||
|
this.$emit('loadTableData') |
||||||
|
} |
||||||
|
}, |
||||||
|
async showChildListModal() { |
||||||
|
await this.loadChildMeta() |
||||||
|
this.childListModal = true |
||||||
|
}, |
||||||
|
async deleteChild(child) { |
||||||
|
this.dialogShow = true |
||||||
|
this.confirmMessage = 'Do you want to delete the record?' |
||||||
|
this.confirmAction = async (act) => { |
||||||
|
if (act === 'hideDialog') { |
||||||
|
this.dialogShow = false |
||||||
|
} else { |
||||||
|
const id = this.childMeta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => child[c.title]) |
||||||
|
.join('___') |
||||||
|
try { |
||||||
|
await this.$api.data.delete(this.childMeta.id, id) |
||||||
|
this.dialogShow = false |
||||||
|
this.$emit('loadTableData') |
||||||
|
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||||
|
this.$refs.childList.loadData() |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
async unlinkChild(child) { |
||||||
|
if (this.isNew) { |
||||||
|
this.localState.splice(this.localState.indexOf(child), 1) |
||||||
|
this.$emit('update:localState', [...this.localState]) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
await this.loadChildMeta() |
||||||
|
const column = this.childMeta.columns.find((c) => c.id === this.column.colOptions.fk_child_column_id) |
||||||
|
|
||||||
|
if (column.rqd) { |
||||||
|
this.$toast.info('Unlink is not possible, instead add to another record.').goAway(3000) |
||||||
|
return |
||||||
|
} |
||||||
|
const id = this.childMeta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => child[c.title]) |
||||||
|
.join('___') |
||||||
|
await this.$api.dbTableRow.nestedRemove( |
||||||
|
'noco', |
||||||
|
this.projectName, |
||||||
|
this.meta.title, |
||||||
|
this.parentId, |
||||||
|
RelationTypes.HAS_MANY, |
||||||
|
this.column.title, |
||||||
|
id, |
||||||
|
) |
||||||
|
|
||||||
|
this.$emit('loadTableData') |
||||||
|
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||||
|
this.$refs.childList.loadData() |
||||||
|
} |
||||||
|
// } |
||||||
|
// } |
||||||
|
}, |
||||||
|
async loadChildMeta() { |
||||||
|
// todo: optimize |
||||||
|
if (!this.childMeta) { |
||||||
|
await this.$store.dispatch('meta/ActLoadMeta', { |
||||||
|
env: this.nodes.env, |
||||||
|
dbAlias: this.nodes.dbAlias, |
||||||
|
id: this.column.colOptions.fk_related_model_id, |
||||||
|
}) |
||||||
|
} |
||||||
|
}, |
||||||
|
async showNewRecordModal() { |
||||||
|
await this.loadChildMeta() |
||||||
|
this.newRecordModal = true |
||||||
|
}, |
||||||
|
async addChildToParent(child) { |
||||||
|
if (this.isNew && this.localState.every((it) => it[this.childForeignKey] !== child[this.childPrimaryKey])) { |
||||||
|
this.localState.push(child) |
||||||
|
this.$emit('update:localState', [...this.localState]) |
||||||
|
this.$emit('saveRow') |
||||||
|
this.newRecordModal = false |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
const id = this.childMeta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => child[c.title]) |
||||||
|
.join('___') |
||||||
|
this.newRecordModal = false |
||||||
|
await this.$api.dbTableRow.nestedAdd('noco', this.projectName, this.meta.title, this.parentId, 'hm', this.column.title, id) |
||||||
|
|
||||||
|
this.$emit('loadTableData') |
||||||
|
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||||
|
await this.$refs.childList.loadData() |
||||||
|
} |
||||||
|
}, |
||||||
|
async editChild(child) { |
||||||
|
await this.loadChildMeta() |
||||||
|
this.isNewChild = false |
||||||
|
this.expandFormModal = true |
||||||
|
this.selectedChild = child |
||||||
|
setTimeout(() => { |
||||||
|
this.$refs.expandedForm && this.$refs.expandedForm.reload() |
||||||
|
}, 500) |
||||||
|
}, |
||||||
|
async insertAndAddNewChildRecord() { |
||||||
|
this.newRecordModal = false |
||||||
|
await this.loadChildMeta() |
||||||
|
this.isNewChild = true |
||||||
|
this.selectedChild = { |
||||||
|
[this.childForeignKey]: parseIfInteger(this.parentId), |
||||||
|
[( |
||||||
|
this.childMeta.columns.find( |
||||||
|
(c) => |
||||||
|
c.uidt === UITypes.LinkToAnotherRecord && |
||||||
|
c.colOptions && |
||||||
|
this.column.colOptions && |
||||||
|
c.colOptions.fk_child_column_id === this.column.colOptions.fk_child_column_id && |
||||||
|
c.colOptions.fk_parent_column_id === this.column.colOptions.fk_parent_column_id && |
||||||
|
c.colOptions.type === RelationTypes.BELONGS_TO, |
||||||
|
) || {} |
||||||
|
).title]: this.row, |
||||||
|
} |
||||||
|
this.expandFormModal = true |
||||||
|
if (!this.isNew) { |
||||||
|
setTimeout(() => { |
||||||
|
this.$refs.expandedForm && |
||||||
|
this.$refs.expandedForm.$set(this.$refs.expandedForm.changedColumns, this.childForeignKey, true) |
||||||
|
}, 500) |
||||||
|
} |
||||||
|
}, |
||||||
|
getCellValue(cellObj) { |
||||||
|
if (cellObj) { |
||||||
|
if (this.childMeta && this.childPrimaryCol) { |
||||||
|
return cellObj[this.childPrimaryCol] |
||||||
|
} |
||||||
|
return Object.values(cellObj)[1] |
||||||
|
} |
||||||
|
}, |
||||||
|
async saveLocalState(row) { |
||||||
|
let child |
||||||
|
// eslint-disable-next-line no-cond-assign |
||||||
|
while ((child = this.localState.pop())) { |
||||||
|
if (row) { |
||||||
|
const pid = this.meta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => row[c.title]) |
||||||
|
.join('___') |
||||||
|
const id = this.childMeta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => child[c.title]) |
||||||
|
.join('___') |
||||||
|
|
||||||
|
await this.$api.dbTableRow.nestedAdd('noco', this.projectName, this.meta.title, pid, 'hm', this.column.title, id) |
||||||
|
} else { |
||||||
|
await this.addChildToParent(child) |
||||||
|
} |
||||||
|
} |
||||||
|
this.$emit('newRecordsSaved') |
||||||
|
}, |
||||||
|
}, |
||||||
|
} */ |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div class="d-flex d-100 chips-wrapper" :class="{ active }"> |
||||||
|
<!-- <template v-if="!isForm"> --> |
||||||
|
<div class="chips d-flex align-center img-container flex-grow-1 hm-items flex-nowrap"> |
||||||
|
<template v-if="value || localState"> |
||||||
|
<ItemChip v-for="(ch, i) in value || localState" :key="i" :value="ch[primaryValueProp]" /> |
||||||
|
|
||||||
|
<!-- |
||||||
|
:active="active" :item="ch" |
||||||
|
:value="getCellValue(ch)" |
||||||
|
:readonly="isLocked || isPublic" |
||||||
|
@edit="editChild" |
||||||
|
@unlink="unlinkChild " --> |
||||||
|
|
||||||
|
<!-- <span |
||||||
|
v-if="!isLocked && value && value.length === 10" |
||||||
|
class="caption pointer ml-1 grey--text" |
||||||
|
@click="showChildListModal" |
||||||
|
>more... |
||||||
|
</span> --> |
||||||
|
</template> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- <div --> |
||||||
|
<!-- v-if="!isLocked" --> |
||||||
|
<!-- class="actions align-center justify-center px-1 flex-shrink-1" --> |
||||||
|
<!-- :class="{ 'd-none': !active, 'd-flex': active }" --> |
||||||
|
<!-- > --> |
||||||
|
<!-- <x-icon --> |
||||||
|
<!-- v-if="_isUIAllowed('xcDatatableEditable') && (isForm || !isPublic)" --> |
||||||
|
<!-- small --> |
||||||
|
<!-- :color="['primary', 'grey']" --> |
||||||
|
<!-- @click="showNewRecordModal" --> |
||||||
|
<!-- > --> |
||||||
|
<!-- mdi-plus --> |
||||||
|
<!-- </x-icon> --> |
||||||
|
<!-- <x-icon x-small :color="['primary', 'grey']" class="ml-2" @click="showChildListModal"> mdi-arrow-expand </x-icon> --> |
||||||
|
<!-- </div> --> |
||||||
|
<!-- </template> --> |
||||||
|
|
||||||
|
<!-- <ListItems |
||||||
|
v-if="newRecordModal" |
||||||
|
v-model="newRecordModal" |
||||||
|
:size="10" |
||||||
|
:meta="childMeta" |
||||||
|
:primary-col="childPrimaryCol" |
||||||
|
:primary-key="childPrimaryKey" |
||||||
|
:api="childApi" |
||||||
|
:parent-meta="meta" |
||||||
|
:column="column" |
||||||
|
:query-params="{ |
||||||
|
...childQueryParams, |
||||||
|
// check if it needs to bypass to |
||||||
|
// avoid foreign key constraint violation in real relation |
||||||
|
isByPass, |
||||||
|
where: |
||||||
|
// show all for new record |
||||||
|
isNew |
||||||
|
? null |
||||||
|
: // filter out those selected items |
||||||
|
`~not(${childForeignKey},eq,${parentId})` + |
||||||
|
// allow the child with empty key |
||||||
|
`~or(${childForeignKey},is,null)`, |
||||||
|
}" |
||||||
|
:is-public="isPublic" |
||||||
|
:password="password" |
||||||
|
:row-id="parentId" |
||||||
|
@add-new-record="insertAndAddNewChildRecord" |
||||||
|
@add="addChildToParent" |
||||||
|
/> |
||||||
|
|
||||||
|
<ListChildItems |
||||||
|
:is="isForm ? 'list-child-items' : 'list-child-items-modal'" |
||||||
|
v-if="childMeta && (childListModal || isForm)" |
||||||
|
ref="childList" |
||||||
|
v-model="childListModal" |
||||||
|
v-model:local-state="localState" |
||||||
|
:is-form="isForm" |
||||||
|
:is-new="isNew" |
||||||
|
:size="10" |
||||||
|
:meta="childMeta" |
||||||
|
:parent-meta="meta" |
||||||
|
:password="password" |
||||||
|
:primary-col="childPrimaryCol" |
||||||
|
:primary-key="childPrimaryKey" |
||||||
|
:api="childApi" |
||||||
|
:column="column" |
||||||
|
:query-params="{ |
||||||
|
...childQueryParams, |
||||||
|
where: `(${childForeignKey},eq,${parentId})`, |
||||||
|
}" |
||||||
|
:is-public="isPublic" |
||||||
|
:row-id="parentId" |
||||||
|
type="hm" |
||||||
|
@new-record="showNewRecordModal" |
||||||
|
@edit="editChild" |
||||||
|
@unlink="unlinkChild" |
||||||
|
@delete="deleteChild" |
||||||
|
/> |
||||||
|
|
||||||
|
<DlgLabelSubmitCancel |
||||||
|
v-if="dialogShow" |
||||||
|
type="primary" |
||||||
|
:actions-mtd="confirmAction" |
||||||
|
:dialog-show="dialogShow" |
||||||
|
:heading="confirmMessage" |
||||||
|
/> |
||||||
|
|
||||||
|
<v-dialog v-model="expandFormModal" :overlay-opacity="0.8" width="1000px" max-width="100%" class="mx-auto"> |
||||||
|
<component |
||||||
|
:is="form" |
||||||
|
v-if="selectedChild" |
||||||
|
ref="expandedForm" |
||||||
|
v-model="selectedChild" |
||||||
|
:db-alias="nodes.dbAlias" |
||||||
|
:has-many="childMeta.hasMany" |
||||||
|
:belongs-to="childMeta.belongsTo" |
||||||
|
:table="childMeta.table_name" |
||||||
|
v-model:is-new="isNewChild" |
||||||
|
:old-row="{ ...selectedChild }" |
||||||
|
:meta="childMeta" |
||||||
|
:primary-value-column="childPrimaryCol" |
||||||
|
:api="childApi" |
||||||
|
:available-columns="childAvailableColumns" |
||||||
|
icon-color="warning" |
||||||
|
:nodes="nodes" |
||||||
|
:query-params="childQueryParams" |
||||||
|
:disabled-columns="disabledChildColumns" |
||||||
|
:breadcrumbs="breadcrumbs" |
||||||
|
@cancel=" |
||||||
|
selectedChild = null |
||||||
|
expandFormModal = false |
||||||
|
" |
||||||
|
@input="onChildSave" |
||||||
|
/> |
||||||
|
</v-dialog> |
||||||
|
--> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.items-container { |
||||||
|
overflow-x: visible; |
||||||
|
max-height: min(500px, 60vh); |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.primary-value { |
||||||
|
.primary-key { |
||||||
|
display: none; |
||||||
|
margin-left: 0.5em; |
||||||
|
} |
||||||
|
|
||||||
|
&:hover .primary-key { |
||||||
|
display: inline; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.child-card { |
||||||
|
cursor: pointer; |
||||||
|
|
||||||
|
&:hover { |
||||||
|
box-shadow: 0 0 0.2em var(--v-textColor-lighten5); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.hm-items { |
||||||
|
//min-width: 200px; |
||||||
|
//max-width: 400px; |
||||||
|
flex-wrap: wrap; |
||||||
|
row-gap: 3px; |
||||||
|
gap: 3px; |
||||||
|
margin: 3px auto; |
||||||
|
} |
||||||
|
|
||||||
|
::v-deep { |
||||||
|
.unlink-icon { |
||||||
|
padding: 0px 1px 2px 1px; |
||||||
|
margin-top: 2px; |
||||||
|
margin-right: -2px; |
||||||
|
} |
||||||
|
|
||||||
|
.search-field { |
||||||
|
input { |
||||||
|
max-height: 28px !important; |
||||||
|
} |
||||||
|
|
||||||
|
.v-input__slot { |
||||||
|
min-height: auto !important; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.chips-wrapper { |
||||||
|
.chips { |
||||||
|
max-width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
&.active { |
||||||
|
.chips { |
||||||
|
max-width: calc(100% - 44px); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
<!-- |
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Naveen MR <oof1lab@gmail.com> |
||||||
|
* @author Pranav C Balan <pranavxc@gmail.com> |
||||||
|
* @author Wing-Kam Wong <wingkwong.code@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
--> |
@ -0,0 +1,183 @@ |
|||||||
|
<script> |
||||||
|
/*import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' |
||||||
|
import TableCell from '../Cell' |
||||||
|
import ItemChip from '~/components/project/spreadsheet/components/virtualCell/components/ItemChip' |
||||||
|
export default { |
||||||
|
name: 'LookupCell', |
||||||
|
components: { |
||||||
|
TableCell, |
||||||
|
// ListChildItemsModal, |
||||||
|
ItemChip, |
||||||
|
}, |
||||||
|
props: { |
||||||
|
meta: [Object], |
||||||
|
metas: [Object], |
||||||
|
column: [Object], |
||||||
|
nodes: [Object], |
||||||
|
row: [Object], |
||||||
|
api: [Object, Function], |
||||||
|
sqlUi: [Object, Function], |
||||||
|
active: Boolean, |
||||||
|
isNew: Boolean, |
||||||
|
isForm: Boolean, |
||||||
|
value: [Object, Array, String, Number], |
||||||
|
}, |
||||||
|
data: () => ({ |
||||||
|
UITypes, |
||||||
|
lookupListModal: false, |
||||||
|
lookupTableMeta: null, |
||||||
|
lookupColumnMeta: null, |
||||||
|
isVirtualCol, |
||||||
|
RelationTypes, |
||||||
|
}), |
||||||
|
computed: { |
||||||
|
virtualCell() { |
||||||
|
return this.lookupColumnMeta && isVirtualCol(this.lookupColumnMeta) |
||||||
|
? () => import('~/components/project/spreadsheet/components/VirtualCell') |
||||||
|
: 'div' |
||||||
|
}, |
||||||
|
// todo : optimize |
||||||
|
lookupApi() { |
||||||
|
// return this.column && this.$ncApis.get({ |
||||||
|
// env: this.nodes.env, |
||||||
|
// dbAlias: this.nodes.dbAlias, |
||||||
|
// table: this.column.lk.ltn |
||||||
|
// }) |
||||||
|
}, |
||||||
|
lookUpMeta() { |
||||||
|
// return this.metas ? this.metas[this.column.lk.ltn] : this.$store.state.meta.metas[this.column.lk.ltn] |
||||||
|
}, |
||||||
|
|
||||||
|
assocMeta() { |
||||||
|
// return this.column.lk.type === 'mm' && (this.metas ? this.metas[this.column.lk.vtn] : this.$store.state.meta.metas[this.column.lk.vtn]) |
||||||
|
}, |
||||||
|
lookUpColumnAlias() { |
||||||
|
if (!this.lookUpMeta || !this.column.lk.lcn) { |
||||||
|
return |
||||||
|
} |
||||||
|
return (this.lookUpMeta.columns.find((cl) => cl.column_name === this.column.lk.lcn) || {}).title |
||||||
|
}, |
||||||
|
lookUpColumn() { |
||||||
|
if (!this.lookUpMeta || !this.column.lk.lcn) { |
||||||
|
return |
||||||
|
} |
||||||
|
return this.lookUpMeta.columns.find((cl) => cl.column_name === this.column.lk.lcn) || {} |
||||||
|
}, |
||||||
|
localValueObj() {}, |
||||||
|
localValue() { |
||||||
|
return this.value && (Array.isArray(this.value) ? this.value : [this.value]) |
||||||
|
}, |
||||||
|
queryParams() {}, |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.loadLookupMeta() |
||||||
|
this.loadLookupColumnMeta() |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
async loadLookupColumnMeta() { |
||||||
|
const relationColumn = this.meta.columns.find((c) => c.id === this.column.colOptions.fk_relation_column_id) |
||||||
|
this.lookupTableMeta = await this.$store.dispatch('meta/ActLoadMeta', { id: relationColumn.colOptions.fk_related_model_id }) |
||||||
|
this.lookupColumnMeta = this.lookupTableMeta.columns.find((c) => c.id === this.column.colOptions.fk_lookup_column_id) |
||||||
|
}, |
||||||
|
|
||||||
|
async loadLookupMeta() {}, |
||||||
|
showLookupListModal() { |
||||||
|
this.lookupListModal = true |
||||||
|
}, |
||||||
|
}, |
||||||
|
}*/ |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div class="d-flex flex-wrap wrapper"> |
||||||
|
<!-- <template v-if="lookupColumnMeta"> |
||||||
|
<template v-if="isVirtualCol(lookupColumnMeta)"> |
||||||
|
<template |
||||||
|
:is="virtualCell" |
||||||
|
v-if=" |
||||||
|
lookupColumnMeta.uidt === UITypes.LinkToAnotherRecord && |
||||||
|
lookupColumnMeta.colOptions.type === RelationTypes.BELONGS_TO && |
||||||
|
Array.isArray(value) |
||||||
|
" |
||||||
|
> |
||||||
|
<div |
||||||
|
:is="virtualCell" |
||||||
|
v-for="(v, i) in value" |
||||||
|
:key="i" |
||||||
|
:is-public="true" |
||||||
|
:metas="metas" |
||||||
|
:is-locked="true" |
||||||
|
:column="lookupColumnMeta" |
||||||
|
:row="{ [lookupColumnMeta.title]: v }" |
||||||
|
:nodes="nodes" |
||||||
|
:meta="lookupTableMeta" |
||||||
|
:sql-ui="sqlUi" |
||||||
|
/> |
||||||
|
</template> |
||||||
|
<div |
||||||
|
:is="virtualCell" |
||||||
|
v-else |
||||||
|
:is-public="true" |
||||||
|
:metas="metas" |
||||||
|
:is-locked="true" |
||||||
|
:column="lookupColumnMeta" |
||||||
|
:row="{ [lookupColumnMeta.title]: value }" |
||||||
|
:nodes="nodes" |
||||||
|
:meta="lookupTableMeta" |
||||||
|
:sql-ui="sqlUi" |
||||||
|
/> |
||||||
|
</template> |
||||||
|
<template v-else> |
||||||
|
<template v-if="localValue"> |
||||||
|
<ItemChip |
||||||
|
v-for="(value, i) in localValue" |
||||||
|
:key="i" |
||||||
|
style="margin: 1.5px" |
||||||
|
:active="active" |
||||||
|
:value="value" |
||||||
|
:readonly="true" |
||||||
|
> |
||||||
|
<TableCell |
||||||
|
:is-locked="true" |
||||||
|
:column="lookupColumnMeta" |
||||||
|
:meta="lookupTableMeta" |
||||||
|
:db-alias="nodes.dbAlias" |
||||||
|
:value="value" |
||||||
|
:sql-ui="sqlUi" |
||||||
|
/> |
||||||
|
</ItemChip> |
||||||
|
</template> |
||||||
|
</template> |
||||||
|
</template>--> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.wrapper { |
||||||
|
flex-wrap: wrap; |
||||||
|
} |
||||||
|
</style> |
||||||
|
<!-- |
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Naveen MR <oof1lab@gmail.com> |
||||||
|
* @author Pranav C Balan <pranavxc@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
--> |
@ -0,0 +1,613 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import type { ColumnType } from "nocodb-sdk"; |
||||||
|
import ItemChip from "./components/ItemChip.vue"; |
||||||
|
import useManyToMany from "~/composables/useManyToMany"; |
||||||
|
|
||||||
|
const column = inject<ColumnType>("column"); |
||||||
|
const value = inject("value"); |
||||||
|
|
||||||
|
const { childMeta, loadChildMeta, primaryValueProp } = useManyToMany(column as ColumnType); |
||||||
|
await loadChildMeta(); |
||||||
|
|
||||||
|
/* import { RelationTypes, UITypes, isSystemColumn } from 'nocodb-sdk' |
||||||
|
import DlgLabelSubmitCancel from '~/components/utils/DlgLabelSubmitCancel' |
||||||
|
import ListItems from '~/components/project/spreadsheet/components/virtualCell/components/ListItems' |
||||||
|
import ListChildItems from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItems' |
||||||
|
import listChildItemsModal from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItemsModal' |
||||||
|
import { parseIfInteger } from '@/helpers' |
||||||
|
import ItemChip from '~/components/project/spreadsheet/components/virtualCell/components/ItemChip' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'ManyToManyCell', |
||||||
|
components: { ListChildItems, ItemChip, ListItems, DlgLabelSubmitCancel, ListChildItemsModal: listChildItemsModal }, |
||||||
|
props: { |
||||||
|
isLocked: Boolean, |
||||||
|
breadcrumbs: { |
||||||
|
type: Array, |
||||||
|
default() { |
||||||
|
return [] |
||||||
|
}, |
||||||
|
}, |
||||||
|
value: [Object, Array], |
||||||
|
meta: [Object], |
||||||
|
mm: Object, |
||||||
|
nodes: [Object], |
||||||
|
row: [Object], |
||||||
|
api: [Object, Function], |
||||||
|
sqlUi: [Object, Function], |
||||||
|
active: Boolean, |
||||||
|
isNew: Boolean, |
||||||
|
isForm: Boolean, |
||||||
|
required: Boolean, |
||||||
|
isPublic: Boolean, |
||||||
|
metas: Object, |
||||||
|
password: String, |
||||||
|
column: Object, |
||||||
|
}, |
||||||
|
data: () => ({ |
||||||
|
isNewChild: false, |
||||||
|
newRecordModal: false, |
||||||
|
childListModal: false, |
||||||
|
// childMeta: null, |
||||||
|
// assocMeta: null, |
||||||
|
childList: null, |
||||||
|
dialogShow: false, |
||||||
|
confirmAction: null, |
||||||
|
confirmMessage: '', |
||||||
|
selectedChild: null, |
||||||
|
expandFormModal: false, |
||||||
|
localState: [], |
||||||
|
}), |
||||||
|
computed: { |
||||||
|
getCellValue() { |
||||||
|
return (cellObj) => { |
||||||
|
if (cellObj) { |
||||||
|
if (this.childPrimaryCol) { |
||||||
|
return cellObj[this.childPrimaryCol] |
||||||
|
} |
||||||
|
return Object.values(cellObj)[1] |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
childMeta() { |
||||||
|
return this.metas |
||||||
|
? this.metas[this.column.colOptions.fk_related_model_id] |
||||||
|
: this.$store.state.meta.metas[this.column.colOptions.fk_related_model_id] |
||||||
|
}, |
||||||
|
assocMeta() { |
||||||
|
return this.metas |
||||||
|
? this.metas[this.column.colOptions.fk_mm_model_id] |
||||||
|
: this.$store.state.meta.metas[this.column.colOptions.fk_mm_model_id] |
||||||
|
}, |
||||||
|
// todo : optimize |
||||||
|
childApi() { |
||||||
|
// return this.childMeta && this.$ncApis.get({ |
||||||
|
// env: this.nodes.env, |
||||||
|
// dbAlias: this.nodes.dbAlias, |
||||||
|
// id: this.column.colOptions.fk_related_model_id |
||||||
|
// }) |
||||||
|
// |
||||||
|
// return this.childMeta && this.childMeta.title |
||||||
|
// ? ApiFactory.create( |
||||||
|
// this.$store.getters['project/GtrProjectType'], |
||||||
|
// this.childMeta.title, |
||||||
|
// this.childMeta.columns, |
||||||
|
// this, |
||||||
|
// this.childMeta |
||||||
|
// ) |
||||||
|
// : null |
||||||
|
}, |
||||||
|
// todo : optimize |
||||||
|
assocApi() { |
||||||
|
// return this.childMeta && this.$ncApis.get({ |
||||||
|
// env: this.nodes.env, |
||||||
|
// dbAlias: this.nodes.dbAlias, |
||||||
|
// id: this.column.colOptions.fk_mm_model_id |
||||||
|
// }) |
||||||
|
// return this.assocMeta && this.assocMeta.title |
||||||
|
// ? ApiFactory.create( |
||||||
|
// this.$store.getters['project/GtrProjectType'], |
||||||
|
// this.assocMeta.title, |
||||||
|
// this.assocMeta.columns, |
||||||
|
// this, |
||||||
|
// this.assocMeta |
||||||
|
// ) |
||||||
|
// : null |
||||||
|
}, |
||||||
|
childPrimaryCol() { |
||||||
|
return this.childMeta && (this.childMeta.columns.find((c) => c.pv) || {}).title |
||||||
|
}, |
||||||
|
childPrimaryKey() { |
||||||
|
return this.childMeta && (this.childMeta.columns.find((c) => c.pk) || {}).title |
||||||
|
}, |
||||||
|
parentPrimaryKey() { |
||||||
|
return this.meta && (this.meta.columns.find((c) => c.pk) || {}).title |
||||||
|
}, |
||||||
|
childQueryParams() { |
||||||
|
if (!this.childMeta) { |
||||||
|
return {} |
||||||
|
} |
||||||
|
// todo: use reduce |
||||||
|
return { |
||||||
|
hm: |
||||||
|
(this.childMeta && |
||||||
|
this.childMeta.v && |
||||||
|
this.childMeta.v |
||||||
|
.filter((v) => v.hm) |
||||||
|
.map(({ hm }) => hm.table_name) |
||||||
|
.join()) || |
||||||
|
'', |
||||||
|
bt: |
||||||
|
(this.childMeta && |
||||||
|
this.childMeta.v && |
||||||
|
this.childMeta.v |
||||||
|
.filter((v) => v.bt) |
||||||
|
.map(({ bt }) => bt.rtn) |
||||||
|
.join()) || |
||||||
|
'', |
||||||
|
mm: |
||||||
|
(this.childMeta && |
||||||
|
this.childMeta.v && |
||||||
|
this.childMeta.v |
||||||
|
.filter((v) => v.mm) |
||||||
|
.map(({ mm }) => mm.rtn) |
||||||
|
.join()) || |
||||||
|
'', |
||||||
|
} |
||||||
|
}, |
||||||
|
conditionGraph() { |
||||||
|
// if (!this.childMeta || !this.assocMeta) { return null } |
||||||
|
// return { |
||||||
|
// [this.assocMeta.table_name]: { |
||||||
|
// relationType: 'hm', |
||||||
|
// [this.assocMeta.columns.find(c => c.column_name === this.mm.vcn).column_name]: { |
||||||
|
// eq: this.row[this.parentPrimaryKey] |
||||||
|
// } |
||||||
|
// } |
||||||
|
// } |
||||||
|
}, |
||||||
|
childAvailableColumns() { |
||||||
|
if (!this.childMeta) { |
||||||
|
return [] |
||||||
|
} |
||||||
|
|
||||||
|
const columns = [] |
||||||
|
if (this.childMeta.columns) { |
||||||
|
columns.push(...this.childMeta.columns.filter((c) => !isSystemColumn(c))) |
||||||
|
} |
||||||
|
return columns |
||||||
|
}, |
||||||
|
// todo: |
||||||
|
form() { |
||||||
|
return this.selectedChild && !this.isPublic |
||||||
|
? () => import('~/components/project/spreadsheet/components/ExpandedForm') |
||||||
|
: 'span' |
||||||
|
}, |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
async isNew(n, o) { |
||||||
|
if (!n && o) { |
||||||
|
await this.saveLocalState() |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
async mounted() { |
||||||
|
if (this.isForm) { |
||||||
|
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]) |
||||||
|
} |
||||||
|
|
||||||
|
if (this.isNew && this.value) { |
||||||
|
this.localState = [...this.value] |
||||||
|
} |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.loadChildMeta() |
||||||
|
this.loadAssociateTableMeta() |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
async onChildSave(child) { |
||||||
|
if (this.isNewChild) { |
||||||
|
this.isNewChild = false |
||||||
|
await this.addChildToParent(child) |
||||||
|
} else { |
||||||
|
this.$emit('loadTableData') |
||||||
|
} |
||||||
|
}, |
||||||
|
async showChildListModal() { |
||||||
|
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]) |
||||||
|
this.childListModal = true |
||||||
|
}, |
||||||
|
async unlinkChild(child) { |
||||||
|
if (this.isNew) { |
||||||
|
this.localState.splice(this.localState.indexOf(child), 1) |
||||||
|
this.$emit('update:localState', [...this.localState]) |
||||||
|
return |
||||||
|
} |
||||||
|
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]) |
||||||
|
const cid = this.childMeta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => child[c.title]) |
||||||
|
.join('___') |
||||||
|
const pid = this.meta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => this.row[c.title]) |
||||||
|
.join('___') |
||||||
|
|
||||||
|
await this.$api.dbTableRow.nestedRemove('noco', this.projectName, this.meta.title, pid, 'mm', this.column.title, cid) |
||||||
|
|
||||||
|
this.$emit('loadTableData') |
||||||
|
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||||
|
this.$refs.childList.loadData() |
||||||
|
} |
||||||
|
}, |
||||||
|
async removeChild(child) { |
||||||
|
this.dialogShow = true |
||||||
|
this.confirmMessage = 'Do you want to delete the record?' |
||||||
|
this.confirmAction = async (act) => { |
||||||
|
if (act === 'hideDialog') { |
||||||
|
this.dialogShow = false |
||||||
|
} else { |
||||||
|
const id = this.childMeta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => child[c.title]) |
||||||
|
.join('___') |
||||||
|
await this.childApi.delete(id) |
||||||
|
this.dialogShow = false |
||||||
|
this.$emit('loadTableData') |
||||||
|
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||||
|
this.$refs.childList.loadData() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
async loadChildMeta() { |
||||||
|
// todo: optimize |
||||||
|
if (!this.childMeta) { |
||||||
|
await this.$store.dispatch('meta/ActLoadMeta', { |
||||||
|
env: this.nodes.env, |
||||||
|
dbAlias: this.nodes.dbAlias, |
||||||
|
// tn: this.mm.rtn, |
||||||
|
id: this.column.colOptions.fk_related_model_id, |
||||||
|
}) |
||||||
|
// const parentTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||||
|
// env: this.nodes.env, |
||||||
|
// dbAlias: this.nodes.dbAlias |
||||||
|
// }, 'tableXcModelGet', { |
||||||
|
// tn: this.mm.rtn |
||||||
|
// }]); |
||||||
|
// this.childMeta = JSON.parse(parentTableData.meta) |
||||||
|
} |
||||||
|
}, |
||||||
|
async loadAssociateTableMeta() { |
||||||
|
// todo: optimize |
||||||
|
if (!this.assocMeta) { |
||||||
|
await this.$store.dispatch('meta/ActLoadMeta', { |
||||||
|
env: this.nodes.env, |
||||||
|
dbAlias: this.nodes.dbAlias, |
||||||
|
id: this.column.colOptions.fk_mm_model_id, |
||||||
|
}) |
||||||
|
// const assocTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||||
|
// env: this.nodes.env, |
||||||
|
// dbAlias: this.nodes.dbAlias |
||||||
|
// }, 'tableXcModelGet', { |
||||||
|
// tn: this.mm.vtn |
||||||
|
// }]); |
||||||
|
// this.assocMeta = JSON.parse(assocTableData.meta) |
||||||
|
} |
||||||
|
}, |
||||||
|
async showNewRecordModal() { |
||||||
|
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]) |
||||||
|
this.newRecordModal = true |
||||||
|
// this.list = await this.c hildApi.paginatedList({}) |
||||||
|
}, |
||||||
|
async addChildToParent(child) { |
||||||
|
if (this.isNew && this.localState.every((it) => it[this.childForeignKey] !== child[this.childPrimaryKey])) { |
||||||
|
this.localState.push(child) |
||||||
|
this.$emit('update:localState', [...this.localState]) |
||||||
|
this.$emit('saveRow') |
||||||
|
this.newRecordModal = false |
||||||
|
return |
||||||
|
} |
||||||
|
const cid = this.childMeta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => child[c.title]) |
||||||
|
.join('___') |
||||||
|
const pid = this.meta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => this.row[c.title]) |
||||||
|
.join('___') |
||||||
|
|
||||||
|
// const vcidCol = this.assocMeta.columns.find(c => c.id === this.column.colOptions.fk_mm_parent_column_id).title |
||||||
|
// const vpidCol = this.assocMeta.columns.find(c => c.id === this.column.colOptions.fk_mm_child_column_id).title |
||||||
|
|
||||||
|
await this.$api.dbTableRow.nestedAdd('noco', this.projectName, this.meta.title, pid, 'mm', this.column.title, cid) |
||||||
|
|
||||||
|
try { |
||||||
|
this.$emit('loadTableData') |
||||||
|
} catch (e) { |
||||||
|
// todo: handle |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
this.newRecordModal = false |
||||||
|
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||||
|
this.$refs.childList.loadData() |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
async insertAndAddNewChildRecord() { |
||||||
|
this.newRecordModal = false |
||||||
|
await this.loadChildMeta() |
||||||
|
this.isNewChild = true |
||||||
|
this.selectedChild = { |
||||||
|
[this.childForeignKey]: this.parentId, |
||||||
|
[( |
||||||
|
this.childMeta.columns.find( |
||||||
|
(c) => |
||||||
|
c.uidt === UITypes.LinkToAnotherRecord && |
||||||
|
c.colOptions && |
||||||
|
this.column.colOptions && |
||||||
|
c.colOptions.fk_child_column_id === this.column.colOptions.fk_parent_column_id && |
||||||
|
c.colOptions.fk_parent_column_id === this.column.colOptions.fk_child_column_id && |
||||||
|
c.colOptions.fk_mm_model_id === this.column.colOptions.fk_mm_model_id && |
||||||
|
c.colOptions.type === RelationTypes.MANY_TO_MANY, |
||||||
|
) || {} |
||||||
|
).title]: [this.row], |
||||||
|
} |
||||||
|
this.expandFormModal = true |
||||||
|
setTimeout(() => { |
||||||
|
this.$refs.expandedForm && |
||||||
|
this.$refs.expandedForm.$set(this.$refs.expandedForm.changedColumns, this.childForeignKey, true) |
||||||
|
}, 500) |
||||||
|
}, |
||||||
|
async editChild(child) { |
||||||
|
await this.loadChildMeta() |
||||||
|
this.isNewChild = false |
||||||
|
this.selectedChild = child |
||||||
|
this.expandFormModal = true |
||||||
|
setTimeout(() => { |
||||||
|
this.$refs.expandedForm && this.$refs.expandedForm.reload() |
||||||
|
}, 500) |
||||||
|
}, |
||||||
|
async saveLocalState(row) { |
||||||
|
let child |
||||||
|
// eslint-disable-next-line no-cond-assign |
||||||
|
while ((child = this.localState.pop())) { |
||||||
|
if (row) { |
||||||
|
const cid = this.childMeta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => child[c.title]) |
||||||
|
.join('___') |
||||||
|
const pid = this.meta.columns |
||||||
|
.filter((c) => c.pk) |
||||||
|
.map((c) => row[c.title]) |
||||||
|
.join('___') |
||||||
|
|
||||||
|
await this.$api.dbTableRow.nestedAdd('noco', this.projectName, this.meta.title, pid, 'mm', this.column.title, cid) |
||||||
|
} else { |
||||||
|
await this.addChildToParent(child) |
||||||
|
} |
||||||
|
} |
||||||
|
this.$emit('newRecordsSaved') |
||||||
|
}, |
||||||
|
}, |
||||||
|
} */ |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div class="d-flex d-100 chips-wrapper" :class="{ active }"> |
||||||
|
<!-- <template v-if="!isForm"> --> |
||||||
|
<div class="chips d-flex align-center img-container flex-grow-1 hm-items flex-nowrap"> |
||||||
|
<template v-if="value || localState"> |
||||||
|
<ItemChip |
||||||
|
v-for="(v, j) in value || localState" |
||||||
|
:key="j" |
||||||
|
:item="v" |
||||||
|
:value="v[primaryValueProp]" |
||||||
|
/> |
||||||
|
|
||||||
|
<!-- :active="active" |
||||||
|
:readonly="isLocked || isPublic" |
||||||
|
@edit="editChild" |
||||||
|
@unlink="unlinkChild" --> |
||||||
|
</template> |
||||||
|
<span v-if="!isLocked && value && value.length === 10" class="caption pointer ml-1 grey--text" @click="showChildListModal" |
||||||
|
>more...</span |
||||||
|
> |
||||||
|
</div> |
||||||
|
<!-- <div --> |
||||||
|
<!-- v-if="!isLocked" --> |
||||||
|
<!-- class="actions align-center justify-center px-1 flex-shrink-1" --> |
||||||
|
<!-- :class="{ 'd-none': !active, 'd-flex': active }" --> |
||||||
|
<!-- > --> |
||||||
|
<!-- <x-icon --> |
||||||
|
<!-- v-if="_isUIAllowed('xcDatatableEditable') && (isForm || !isPublic)" --> |
||||||
|
<!-- small --> |
||||||
|
<!-- :color="['primary', 'grey']" --> |
||||||
|
<!-- @click="showNewRecordModal" --> |
||||||
|
<!-- > --> |
||||||
|
<!-- mdi-plus --> |
||||||
|
<!-- </x-icon> --> |
||||||
|
<!-- <x-icon x-small :color="['primary', 'grey']" class="ml-2" @click="showChildListModal"> mdi-arrow-expand </x-icon> --> |
||||||
|
<!-- </div> --> |
||||||
|
<!-- </template>--> |
||||||
|
|
||||||
|
<!-- <ListItems |
||||||
|
v-if="newRecordModal" |
||||||
|
v-model="newRecordModal" |
||||||
|
:hm="true" |
||||||
|
:size="10" |
||||||
|
:column="column" |
||||||
|
:meta="childMeta" |
||||||
|
:primary-col="childPrimaryCol" |
||||||
|
:primary-key="childPrimaryKey" |
||||||
|
:parent-meta="meta" |
||||||
|
:api="api" |
||||||
|
:mm="mm" |
||||||
|
:tn="mm && mm.rtn" |
||||||
|
:parent-id="row && row[parentPrimaryKey]" |
||||||
|
:is-public="isPublic" |
||||||
|
:query-params="childQueryParams" |
||||||
|
:password="password" |
||||||
|
:row-id="row && row[parentPrimaryKey]" |
||||||
|
@add-new-record="insertAndAddNewChildRecord" |
||||||
|
@add="addChildToParent" |
||||||
|
/> |
||||||
|
|
||||||
|
<ListChildItems |
||||||
|
:is="isForm ? 'list-child-items' : 'list-child-items-modal'" |
||||||
|
v-if="childMeta && assocMeta && (isForm || childListModal)" |
||||||
|
ref="childList" |
||||||
|
v-model="childListModal" |
||||||
|
:is-form="isForm" |
||||||
|
:is-new="isNew" |
||||||
|
:size="10" |
||||||
|
:meta="childMeta" |
||||||
|
:parent-meta="meta" |
||||||
|
:primary-col="childPrimaryCol" |
||||||
|
:primary-key="childPrimaryKey" |
||||||
|
:api="childApi" |
||||||
|
:mm="mm" |
||||||
|
:parent-id="row && row[parentPrimaryKey]" |
||||||
|
:query-params="{ ...childQueryParams, conditionGraph }" |
||||||
|
:local-state="localState" |
||||||
|
:is-public="isPublic" |
||||||
|
:row-id="row && row[parentPrimaryKey]" |
||||||
|
:column="column" |
||||||
|
type="mm" |
||||||
|
:password="password" |
||||||
|
@new-record="showNewRecordModal" |
||||||
|
@edit="editChild" |
||||||
|
@unlink="unlinkChild" |
||||||
|
/> |
||||||
|
<DlgLabelSubmitCancel |
||||||
|
v-if="dialogShow" |
||||||
|
type="primary" |
||||||
|
:actions-mtd="confirmAction" |
||||||
|
:dialog-show="dialogShow" |
||||||
|
:heading="confirmMessage" |
||||||
|
/> |
||||||
|
|
||||||
|
<!– todo : move to list item component –> |
||||||
|
<v-dialog |
||||||
|
v-if="selectedChild && !isPublic" |
||||||
|
v-model="expandFormModal" |
||||||
|
:overlay-opacity="0.8" |
||||||
|
width="1000px" |
||||||
|
max-width="100%" |
||||||
|
class="mx-auto" |
||||||
|
> |
||||||
|
<component |
||||||
|
:is="form" |
||||||
|
v-if="selectedChild" |
||||||
|
ref="expandedForm" |
||||||
|
v-model="selectedChild" |
||||||
|
:db-alias="nodes.dbAlias" |
||||||
|
:has-many="childMeta.hasMany" |
||||||
|
:belongs-to="childMeta.belongsTo" |
||||||
|
v-model:is-new="isNewChild" |
||||||
|
:table="childMeta.table_name" |
||||||
|
:old-row="{ ...selectedChild }" |
||||||
|
:meta="childMeta" |
||||||
|
:primary-value-column="childPrimaryCol" |
||||||
|
:available-columns="childAvailableColumns" |
||||||
|
icon-color="warning" |
||||||
|
:nodes="nodes" |
||||||
|
:query-params="childQueryParams" |
||||||
|
:breadcrumbs="breadcrumbs" |
||||||
|
@cancel=" |
||||||
|
selectedChild = null |
||||||
|
expandFormModal = false |
||||||
|
" |
||||||
|
@input="onChildSave" |
||||||
|
/> |
||||||
|
</v-dialog> --> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.items-container { |
||||||
|
overflow-x: visible; |
||||||
|
max-height: min(500px, 60vh); |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.primary-value { |
||||||
|
.primary-key { |
||||||
|
display: none; |
||||||
|
margin-left: 0.5em; |
||||||
|
} |
||||||
|
|
||||||
|
&:hover .primary-key { |
||||||
|
display: inline; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.child-list-modal { |
||||||
|
position: relative; |
||||||
|
|
||||||
|
.remove-child-icon { |
||||||
|
position: absolute; |
||||||
|
right: 10px; |
||||||
|
top: 10px; |
||||||
|
bottom: 10px; |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
|
||||||
|
&:hover .remove-child-icon { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.child-card { |
||||||
|
cursor: pointer; |
||||||
|
|
||||||
|
&:hover { |
||||||
|
box-shadow: 0 0 0.2em var(--v-textColor-lighten5); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.hm-items { |
||||||
|
//min-width: 200px; |
||||||
|
//max-width: 400px; |
||||||
|
flex-wrap: wrap; |
||||||
|
row-gap: 3px; |
||||||
|
gap: 3px; |
||||||
|
margin: 3px auto; |
||||||
|
} |
||||||
|
|
||||||
|
.chips-wrapper { |
||||||
|
.chips { |
||||||
|
max-width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
&.active { |
||||||
|
.chips { |
||||||
|
max-width: calc(100% - 44px); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
<!-- |
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Naveen MR <oof1lab@gmail.com> |
||||||
|
* @author Pranav C Balan <pranavxc@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
--> |
@ -0,0 +1,9 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
const { value } = defineProps<{ value: any }>() |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<span> |
||||||
|
{{ value }} |
||||||
|
</span> |
||||||
|
</template> |
@ -0,0 +1,48 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import MdiCloseThickIcon from '~icons/mdi/close-thick' |
||||||
|
/* export default { |
||||||
|
name: 'ItemChip', |
||||||
|
props: { |
||||||
|
value: [String, Number, Boolean], |
||||||
|
active: Boolean, |
||||||
|
item: Object, |
||||||
|
readonly: Boolean, |
||||||
|
}, |
||||||
|
} */ |
||||||
|
|
||||||
|
const { value, active, item, readonly } = defineProps({ |
||||||
|
value: [String, Number, Boolean], |
||||||
|
active: Boolean, |
||||||
|
item: Object, |
||||||
|
readonly: Boolean, |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<v-chip |
||||||
|
class="chip" |
||||||
|
:class="{ active }" |
||||||
|
small |
||||||
|
text-color="textColor" |
||||||
|
:color="isDark ? '' : 'primary lighten-5'" |
||||||
|
@click="!readonly && active && $emit('edit', item)" |
||||||
|
> |
||||||
|
<span class="name" :title="value">{{ value }}</span> |
||||||
|
|
||||||
|
<!-- && _isUIAllowed('xcDatatableEditable') --> |
||||||
|
<div v-show="active" v-if="!readonly" class="mr-n1 ml-2"> |
||||||
|
<MdiCloseThickIcon class="unlink-icon" @click.stop="$emit('unlink', item)"> </MdiCloseThickIcon> |
||||||
|
</div> |
||||||
|
</v-chip> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.chip { |
||||||
|
max-width: max(100%, 60px); |
||||||
|
|
||||||
|
.name { |
||||||
|
text-overflow: ellipsis; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,252 @@ |
|||||||
|
<script> |
||||||
|
import { RelationTypes } from 'nocodb-sdk' |
||||||
|
import Pagination from '~/components/project/spreadsheet/components/Pagination' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'ListChildItems', |
||||||
|
components: { Pagination }, |
||||||
|
props: { |
||||||
|
readOnly: Boolean, |
||||||
|
isForm: Boolean, |
||||||
|
bt: [Object], |
||||||
|
localState: [Array], |
||||||
|
isNew: Boolean, |
||||||
|
value: Boolean, |
||||||
|
title: { |
||||||
|
type: String, |
||||||
|
default: 'Link Record', |
||||||
|
}, |
||||||
|
queryParams: { |
||||||
|
type: Object, |
||||||
|
default() { |
||||||
|
return {} |
||||||
|
}, |
||||||
|
}, |
||||||
|
primaryKey: String, |
||||||
|
primaryCol: String, |
||||||
|
meta: Object, |
||||||
|
parentMeta: Object, |
||||||
|
size: Number, |
||||||
|
api: [Object, Function], |
||||||
|
mm: [Object, Boolean], |
||||||
|
isPublic: Boolean, |
||||||
|
rowId: [String, Number], |
||||||
|
column: Object, |
||||||
|
type: String, |
||||||
|
password: String, |
||||||
|
}, |
||||||
|
data: () => ({ |
||||||
|
RelationTypes, |
||||||
|
data: null, |
||||||
|
page: 1, |
||||||
|
}), |
||||||
|
computed: { |
||||||
|
isDataAvail() { |
||||||
|
return (this.data && this.data.list && this.data.list.length) || (this.localState && this.localState.length) |
||||||
|
}, |
||||||
|
show: { |
||||||
|
set(v) { |
||||||
|
this.$emit('input', v) |
||||||
|
}, |
||||||
|
get() { |
||||||
|
return this.value |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
queryParams() { |
||||||
|
this.loadData() |
||||||
|
}, |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.loadData() |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
async loadData() { |
||||||
|
if (!this.isForm && this.isPublic && this.$route.params.id) { |
||||||
|
if (this.column && this.column.colOptions && this.rowId) { |
||||||
|
this.data = await this.$api.public.dataNestedList( |
||||||
|
this.$route.params.id, |
||||||
|
this.rowId, |
||||||
|
this.column.colOptions.type, |
||||||
|
this.column.fk_column_id || this.column.id, |
||||||
|
{ |
||||||
|
limit: this.size, |
||||||
|
offset: this.size * (this.page - 1), |
||||||
|
}, |
||||||
|
{}, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if (this.isNew) { |
||||||
|
return |
||||||
|
} |
||||||
|
if (this.column && this.column.colOptions) { |
||||||
|
this.data = await this.$api.dbTableRow.nestedList( |
||||||
|
'noco', |
||||||
|
this.projectName, |
||||||
|
this.parentMeta.title, |
||||||
|
this.rowId, |
||||||
|
this.column.colOptions.type, |
||||||
|
this.column.title, |
||||||
|
{ |
||||||
|
limit: this.size, |
||||||
|
offset: this.size * (this.page - 1), |
||||||
|
}, |
||||||
|
) |
||||||
|
} else { |
||||||
|
this.data = await this.$api.dbTableRow.list('noco', this.projectName, this.meta.title, { |
||||||
|
limit: this.size, |
||||||
|
offset: this.size * (this.page - 1), |
||||||
|
...this.queryParams, |
||||||
|
}) |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<v-card width="600" color=""> |
||||||
|
<v-card-title v-if="!isForm" class="textColor--text mx-2" :class="{ 'py-2': isForm }"> |
||||||
|
<span v-if="!isForm">{{ meta ? meta.title : 'Children' }}</span> |
||||||
|
<v-spacer /> |
||||||
|
<v-icon small class="mr-1" @click="loadData()"> mdi-reload </v-icon> |
||||||
|
<v-btn |
||||||
|
v-if="(isForm || !isPublic) && !readOnly && (isPublic || _isUIAllowed('xcDatatableEditable'))" |
||||||
|
small |
||||||
|
class="caption" |
||||||
|
color="primary" |
||||||
|
@click="$emit('new-record')" |
||||||
|
> |
||||||
|
<v-icon small> mdi-link </v-icon> Link to '{{ meta.title }}' |
||||||
|
</v-btn> |
||||||
|
</v-card-title> |
||||||
|
<v-card-text> |
||||||
|
<div class="items-container pt-2 mb-n4" :class="{ 'mx-n2': isForm }"> |
||||||
|
<div v-if="!readOnly && (isPublic || _isUIAllowed('xcDatatableEditable'))" class="text-right mb-2 mt-n2 mx-2"> |
||||||
|
<v-btn v-if="isForm" x-small class="caption" color="primary" outlined @click="$emit('new-record')"> |
||||||
|
<v-icon x-small> mdi-link </v-icon> Link to '{{ meta.title }}' |
||||||
|
</v-btn> |
||||||
|
</div> |
||||||
|
<template v-if="isDataAvail"> |
||||||
|
<v-card |
||||||
|
v-for="(ch, i) in (data && data.list) || localState" |
||||||
|
:key="i" |
||||||
|
class="mx-2 mb-2 child-list-modal child-card" |
||||||
|
outlined |
||||||
|
@click="!readOnly && $emit('edit', ch)" |
||||||
|
> |
||||||
|
<div class="remove-child-icon d-flex align-center"> |
||||||
|
<x-icon |
||||||
|
v-if="((isPublic && isForm) || (!isPublic && _isUIAllowed('xcDatatableEditable'))) && !readOnly" |
||||||
|
:tooltip="`Unlink this '${meta.title}' from '${parentMeta.title}'`" |
||||||
|
:color="['error', 'grey']" |
||||||
|
small |
||||||
|
icon.class="mr-1 mt-n1" |
||||||
|
@click.stop="$emit('unlink', ch, i)" |
||||||
|
> |
||||||
|
mdi-link-variant-remove |
||||||
|
</x-icon> |
||||||
|
<x-icon |
||||||
|
v-if="!isPublic && type === RelationTypes.HAS_MANY && !readOnly && _isUIAllowed('xcDatatableEditable')" |
||||||
|
:tooltip="`Delete row in '${meta.title}'`" |
||||||
|
:color="['error', 'grey']" |
||||||
|
small |
||||||
|
@click.stop="$emit('delete', ch, i)" |
||||||
|
> |
||||||
|
mdi-delete-outline |
||||||
|
</x-icon> |
||||||
|
</div> |
||||||
|
|
||||||
|
<v-card-title class="primary-value textColor--text text--lighten-2"> |
||||||
|
{{ ch[primaryCol] }} |
||||||
|
<span v-if="primaryKey" class="grey--text caption primary-key ml-1"> (Primary Key : {{ ch[primaryKey] }})</span> |
||||||
|
</v-card-title> |
||||||
|
</v-card> |
||||||
|
</template> |
||||||
|
|
||||||
|
<div |
||||||
|
v-else-if="data || localState" |
||||||
|
class="text-center textLight--text" |
||||||
|
:class="{ 'pt-6 pb-4': !isForm, 'pt-4 pb-3': isForm }" |
||||||
|
> |
||||||
|
No item{{ bt ? '' : 's' }} found |
||||||
|
</div> |
||||||
|
|
||||||
|
<div v-if="isForm" class="mb-2 d-flex align-center justify-center"> |
||||||
|
<Pagination |
||||||
|
v-if="!bt && data && data.pageInfo && data.pageInfo.totalRows > 1" |
||||||
|
v-model="page" |
||||||
|
:size="size" |
||||||
|
:count="data && data.pageInfo && data.pageInfo.totalRows" |
||||||
|
@input="loadData" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</v-card-text> |
||||||
|
<v-card-actions v-if="!isForm" class="justify-center flex-column" :class="{ 'py-0': isForm }"> |
||||||
|
<Pagination |
||||||
|
v-if="!bt && data && data.pageInfo && data.pageInfo.totalRows > 1" |
||||||
|
v-model="page" |
||||||
|
:size="size" |
||||||
|
:count="data && data.pageInfo && data.pageInfo.totalRows" |
||||||
|
class="mb-3" |
||||||
|
@input="loadData" |
||||||
|
/> |
||||||
|
</v-card-actions> |
||||||
|
</v-card> |
||||||
|
<!-- </v-dialog> --> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.child-list-modal { |
||||||
|
position: relative; |
||||||
|
|
||||||
|
.remove-child-icon { |
||||||
|
position: absolute; |
||||||
|
right: 10px; |
||||||
|
top: 10px; |
||||||
|
bottom: 10px; |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
|
||||||
|
&:hover .remove-child-icon { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.items-container { |
||||||
|
overflow-x: visible; |
||||||
|
max-height: min(500px, 60vh); |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
</style> |
||||||
|
|
||||||
|
<!-- |
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Naveen MR <oof1lab@gmail.com> |
||||||
|
* @author Pranav C Balan <pranavxc@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
--> |
@ -0,0 +1,126 @@ |
|||||||
|
<script> |
||||||
|
import ListChildItems from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItems' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'ListChildItemsModal', |
||||||
|
components: { ListChildItems }, |
||||||
|
props: { |
||||||
|
type: String, |
||||||
|
readOnly: Boolean, |
||||||
|
localState: Array, |
||||||
|
isNew: Boolean, |
||||||
|
password: String, |
||||||
|
value: Boolean, |
||||||
|
title: { |
||||||
|
type: String, |
||||||
|
default: 'Link Record', |
||||||
|
}, |
||||||
|
queryParams: { |
||||||
|
type: Object, |
||||||
|
default() { |
||||||
|
return {} |
||||||
|
}, |
||||||
|
}, |
||||||
|
primaryKey: String, |
||||||
|
primaryCol: String, |
||||||
|
meta: Object, |
||||||
|
parentMeta: Object, |
||||||
|
size: Number, |
||||||
|
api: [Object, Function], |
||||||
|
mm: [Object, Boolean], |
||||||
|
isPublic: Boolean, |
||||||
|
rowId: [String, Number], |
||||||
|
column: Object, |
||||||
|
}, |
||||||
|
data: () => ({ |
||||||
|
data: null, |
||||||
|
page: 1, |
||||||
|
}), |
||||||
|
computed: { |
||||||
|
show: { |
||||||
|
set(v) { |
||||||
|
this.$emit('input', v) |
||||||
|
}, |
||||||
|
get() { |
||||||
|
return this.value |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
mounted() {}, |
||||||
|
methods: { |
||||||
|
async loadData() { |
||||||
|
if (this.$refs && this.$refs.child) { |
||||||
|
this.$refs.child.loadData() |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<v-dialog v-model="show" width="600" content-class="dialog"> |
||||||
|
<v-icon small class="close-icon" @click="$emit('input', false)"> mdi-close </v-icon> |
||||||
|
<ListChildItems |
||||||
|
v-if="show" |
||||||
|
ref="child" |
||||||
|
:type="type" |
||||||
|
:row-id="rowId" |
||||||
|
:local-state="localState" |
||||||
|
:is-new="isNew" |
||||||
|
:size="10" |
||||||
|
:meta="meta" |
||||||
|
:password="password" |
||||||
|
:parent-meta="parentMeta" |
||||||
|
:primary-col="primaryCol" |
||||||
|
:primary-key="primaryKey" |
||||||
|
:api="api" |
||||||
|
:query-params="queryParams" |
||||||
|
v-bind="$attrs" |
||||||
|
:read-only="readOnly" |
||||||
|
:is-public="isPublic" |
||||||
|
:column="column" |
||||||
|
v-on="$listeners" |
||||||
|
/> |
||||||
|
</v-dialog> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
::v-deep { |
||||||
|
.dialog { |
||||||
|
position: relative; |
||||||
|
|
||||||
|
.close-icon { |
||||||
|
width: auto; |
||||||
|
position: absolute; |
||||||
|
right: 10px; |
||||||
|
top: 10px; |
||||||
|
z-index: 9; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
|
||||||
|
<!-- |
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Naveen MR <oof1lab@gmail.com> |
||||||
|
* @author Pranav C Balan <pranavxc@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
--> |
@ -0,0 +1,248 @@ |
|||||||
|
<script> |
||||||
|
import Pagination from '~/components/project/spreadsheet/components/Pagination' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'ListItems', |
||||||
|
components: { Pagination }, |
||||||
|
props: { |
||||||
|
value: Boolean, |
||||||
|
tn: String, |
||||||
|
hm: [Object, Function, Boolean], |
||||||
|
title: { |
||||||
|
type: String, |
||||||
|
default: 'Link Record', |
||||||
|
}, |
||||||
|
queryParams: { |
||||||
|
type: Object, |
||||||
|
default() { |
||||||
|
return {} |
||||||
|
}, |
||||||
|
}, |
||||||
|
primaryKey: String, |
||||||
|
primaryCol: String, |
||||||
|
meta: Object, |
||||||
|
size: Number, |
||||||
|
api: [Object, Function], |
||||||
|
mm: [Object, Function], |
||||||
|
parentId: [String, Number], |
||||||
|
parentMeta: [Object], |
||||||
|
isPublic: Boolean, |
||||||
|
password: String, |
||||||
|
column: Object, |
||||||
|
rowId: [Number, String], |
||||||
|
}, |
||||||
|
data: () => ({ |
||||||
|
data: null, |
||||||
|
page: 1, |
||||||
|
query: '', |
||||||
|
}), |
||||||
|
computed: { |
||||||
|
show: { |
||||||
|
set(v) { |
||||||
|
this.$emit('input', v) |
||||||
|
}, |
||||||
|
get() { |
||||||
|
return this.value |
||||||
|
}, |
||||||
|
}, |
||||||
|
hmParentPrimaryValCol() { |
||||||
|
return this.hm && this.parentMeta && this.parentMeta.columns.find((v) => v.pv).title |
||||||
|
}, |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.loadData() |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
async loadData() { |
||||||
|
if (this.isPublic) { |
||||||
|
this.data = await this.$api.public.dataRelationList( |
||||||
|
this.$route.params.id, |
||||||
|
this.column.id, |
||||||
|
{}, |
||||||
|
{ |
||||||
|
headers: { |
||||||
|
'xc-password': this.password, |
||||||
|
}, |
||||||
|
query: { |
||||||
|
limit: this.size, |
||||||
|
offset: this.size * (this.page - 1), |
||||||
|
...this.queryParams, |
||||||
|
}, |
||||||
|
}, |
||||||
|
) |
||||||
|
} else { |
||||||
|
const where = `(${this.primaryCol},like,%${this.query}%)` |
||||||
|
|
||||||
|
if (this.column && this.column.colOptions && this.rowId) { |
||||||
|
this.data = await this.$api.dbTableRow.nestedChildrenExcludedList( |
||||||
|
'noco', |
||||||
|
this.projectName, |
||||||
|
this.parentMeta.title, |
||||||
|
this.rowId, |
||||||
|
this.column.colOptions.type, |
||||||
|
this.column.title, |
||||||
|
{ |
||||||
|
limit: this.size, |
||||||
|
offset: this.size * (this.page - 1), |
||||||
|
where: this.query && `(${this.primaryCol},like,${this.query})`, |
||||||
|
}, |
||||||
|
) |
||||||
|
} else { |
||||||
|
this.data = await this.$api.dbTableRow.list('noco', this.projectName, this.meta.title, { |
||||||
|
limit: this.size, |
||||||
|
offset: this.size * (this.page - 1), |
||||||
|
...this.queryParams, |
||||||
|
where, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<v-dialog v-model="show" width="600" content-class="dialog"> |
||||||
|
<v-icon small class="close-icon" @click="$emit('input', false)"> mdi-close </v-icon> |
||||||
|
<v-card width="600"> |
||||||
|
<v-card-title class="textColor--text mx-2 justify-center"> |
||||||
|
{{ title }} |
||||||
|
</v-card-title> |
||||||
|
|
||||||
|
<v-card-title> |
||||||
|
<v-text-field |
||||||
|
v-model="query" |
||||||
|
hide-details |
||||||
|
dense |
||||||
|
outlined |
||||||
|
placeholder="Filter query" |
||||||
|
class="caption search-field ml-2" |
||||||
|
@keydown.enter="loadData" |
||||||
|
> |
||||||
|
<template #append> |
||||||
|
<x-icon tooltip="Apply filter" small icon.class="mt-1" @click="loadData"> mdi-keyboard-return </x-icon> |
||||||
|
</template> |
||||||
|
</v-text-field> |
||||||
|
<v-spacer /> |
||||||
|
|
||||||
|
<v-icon small class="mr-1" @click="loadData()"> mdi-reload </v-icon> |
||||||
|
<v-btn v-if="!isPublic" small class="caption mr-2" color="primary" @click="$emit('add-new-record')"> |
||||||
|
<v-icon small> mdi-plus </v-icon> New Record |
||||||
|
</v-btn> |
||||||
|
</v-card-title> |
||||||
|
|
||||||
|
<v-card-text> |
||||||
|
<div class="items-container"> |
||||||
|
<template v-if="data && data.list && data.list.length"> |
||||||
|
<v-card v-for="(ch, i) in data.list" :key="i" v-ripple class="ma-2 child-card" outlined @click="$emit('add', ch)"> |
||||||
|
<v-card-text class="primary-value textColor--text text--lighten-2 d-flex"> |
||||||
|
<span class="font-weight-bold"> {{ ch[primaryCol] || (ch && Object.values(ch).slice(0, 1).join()) }} </span> |
||||||
|
<span v-if="primaryKey" class="grey--text caption primary-key">(Primary Key : {{ ch[primaryKey] }})</span> |
||||||
|
<v-spacer /> |
||||||
|
<v-chip v-if="hm && ch[`${hm._rtn}Read`] && ch[`${hm._rtn}Read`][hmParentPrimaryValCol]" x-small> |
||||||
|
{{ ch[`${hm._rtn}Read`][hmParentPrimaryValCol] }} |
||||||
|
</v-chip> |
||||||
|
</v-card-text> |
||||||
|
</v-card> |
||||||
|
</template> |
||||||
|
|
||||||
|
<div v-else-if="data" class="text-center py-15 textLight--text"> |
||||||
|
<!-- No items found --> |
||||||
|
{{ $t('placeholder.noItemsFound') }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</v-card-text> |
||||||
|
<v-card-actions class="justify-center py-2 flex-column"> |
||||||
|
<Pagination |
||||||
|
v-if="data && data.list && data.list.length" |
||||||
|
v-model="page" |
||||||
|
:size="size" |
||||||
|
:count="data && data.pageInfo && data.pageInfo.totalRows" |
||||||
|
class="mb-3" |
||||||
|
@input="loadData" |
||||||
|
/> |
||||||
|
</v-card-actions> |
||||||
|
</v-card> |
||||||
|
</v-dialog> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.child-list-modal { |
||||||
|
position: relative; |
||||||
|
|
||||||
|
.remove-child-icon { |
||||||
|
position: absolute; |
||||||
|
right: 10px; |
||||||
|
top: 10px; |
||||||
|
bottom: 10px; |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
|
||||||
|
&:hover .remove-child-icon { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.child-card { |
||||||
|
cursor: pointer; |
||||||
|
|
||||||
|
&:hover { |
||||||
|
box-shadow: 0 0 0.2em var(--v-textColor-lighten5); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.primary-value { |
||||||
|
.primary-key { |
||||||
|
display: none; |
||||||
|
margin-left: 0.5em; |
||||||
|
} |
||||||
|
|
||||||
|
&:hover .primary-key { |
||||||
|
display: inline; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.items-container { |
||||||
|
overflow-x: visible; |
||||||
|
max-height: min(500px, 60vh); |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
|
||||||
|
::v-deep { |
||||||
|
.dialog { |
||||||
|
position: relative; |
||||||
|
|
||||||
|
.close-icon { |
||||||
|
width: auto; |
||||||
|
position: absolute; |
||||||
|
right: 10px; |
||||||
|
top: 10px; |
||||||
|
z-index: 9; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
<!-- |
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Naveen MR <oof1lab@gmail.com> |
||||||
|
* @author Pranav C Balan <pranavxc@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
--> |
@ -0,0 +1,20 @@ |
|||||||
|
import type { ColumnType, TableType } from 'nocodb-sdk' |
||||||
|
import type LinkToAnotherRecordColumn from '../../nocodb/src/lib/models/LinkToAnotherRecordColumn' |
||||||
|
import useMetas from '~/composables/useMetas' |
||||||
|
|
||||||
|
export default function (column: ColumnType) { |
||||||
|
const { metas, getMeta } = useMetas() |
||||||
|
const parentMeta = computed<TableType>(() => { |
||||||
|
return metas.value?.[(column.colOptions as LinkToAnotherRecordColumn)?.fk_related_model_id as string] |
||||||
|
}) |
||||||
|
|
||||||
|
const loadParentMeta = async () => { |
||||||
|
await getMeta((column.colOptions as LinkToAnotherRecordColumn)?.fk_related_model_id as string) |
||||||
|
} |
||||||
|
|
||||||
|
const primaryValueProp = computed(() => { |
||||||
|
return (parentMeta?.value?.columns?.find((c) => c.pv) || parentMeta?.value?.columns?.[0])?.title |
||||||
|
}) |
||||||
|
|
||||||
|
return { parentMeta, loadParentMeta, primaryValueProp } |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
import type { ColumnType, TableType } from 'nocodb-sdk' |
||||||
|
import type LinkToAnotherRecordColumn from '../../nocodb/src/lib/models/LinkToAnotherRecordColumn' |
||||||
|
import useMetas from '~/composables/useMetas' |
||||||
|
|
||||||
|
export default function (column: ColumnType) { |
||||||
|
const { metas, getMeta } = useMetas() |
||||||
|
const childMeta = computed<TableType>(() => { |
||||||
|
return metas.value?.[(column.colOptions as LinkToAnotherRecordColumn)?.fk_related_model_id as string] |
||||||
|
}) |
||||||
|
|
||||||
|
const loadChildMeta = async () => { |
||||||
|
await getMeta((column.colOptions as LinkToAnotherRecordColumn)?.fk_related_model_id as string) |
||||||
|
} |
||||||
|
|
||||||
|
const primaryValueProp = computed(() => { |
||||||
|
return (childMeta?.value?.columns?.find((c) => c.pv) || childMeta?.value?.columns?.[0])?.title |
||||||
|
}) |
||||||
|
|
||||||
|
return { childMeta, loadChildMeta, primaryValueProp } |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
import type { ColumnType, TableType } from 'nocodb-sdk' |
||||||
|
import type LinkToAnotherRecordColumn from '../../nocodb/src/lib/models/LinkToAnotherRecordColumn' |
||||||
|
import useMetas from '~/composables/useMetas' |
||||||
|
|
||||||
|
export default function (column: ColumnType) { |
||||||
|
const { metas, getMeta } = useMetas() |
||||||
|
const childMeta = computed<TableType>(() => { |
||||||
|
return metas.value?.[(column.colOptions as LinkToAnotherRecordColumn)?.fk_related_model_id as string] |
||||||
|
}) |
||||||
|
|
||||||
|
const loadChildMeta = async () => { |
||||||
|
await getMeta((column.colOptions as LinkToAnotherRecordColumn)?.fk_related_model_id as string) |
||||||
|
} |
||||||
|
|
||||||
|
const primaryValueProp = computed(() => { |
||||||
|
return (childMeta?.value?.columns?.find((c) => c.pv) || childMeta?.value?.columns?.[0])?.title |
||||||
|
}) |
||||||
|
|
||||||
|
return { childMeta, loadChildMeta, primaryValueProp } |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
import { computed } from '@vue/reactivity' |
||||||
|
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk' |
||||||
|
import { RelationTypes, UITypes } from 'nocodb-sdk' |
||||||
|
|
||||||
|
export default function useVirtualCell(column: ColumnType) { |
||||||
|
const isHm = computed( |
||||||
|
() => |
||||||
|
column.uidt === UITypes.LinkToAnotherRecord && (<LinkToAnotherRecordType>column.colOptions).type === RelationTypes.HAS_MANY, |
||||||
|
) |
||||||
|
const isMm = computed( |
||||||
|
() => |
||||||
|
column.uidt === UITypes.LinkToAnotherRecord && |
||||||
|
(<LinkToAnotherRecordType>column.colOptions).type === RelationTypes.MANY_TO_MANY, |
||||||
|
) |
||||||
|
const isBt = computed( |
||||||
|
() => |
||||||
|
column.uidt === UITypes.LinkToAnotherRecord && |
||||||
|
(<LinkToAnotherRecordType>column.colOptions).type === RelationTypes.BELONGS_TO, |
||||||
|
) |
||||||
|
const isLookup = computed(() => column.uidt === UITypes.Lookup) |
||||||
|
const isRollup = computed(() => column.uidt === UITypes.Rollup) |
||||||
|
const isFormula = computed(() => column.uidt === UITypes.Formula) |
||||||
|
|
||||||
|
return { |
||||||
|
isHm, |
||||||
|
isMm, |
||||||
|
isBt, |
||||||
|
isLookup, |
||||||
|
isRollup, |
||||||
|
isFormula, |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue