Browse Source

chore(gui-v2): lint files

Signed-off-by: Braks <78412429+bcakmakoglu@users.noreply.github.com>
pull/2716/head
Braks 2 years ago committed by Pranav C
parent
commit
b59af42164
  1. 1
      packages/nc-gui-v2/app.vue
  2. 61
      packages/nc-gui-v2/components/cell/Attachment.vue
  3. 2
      packages/nc-gui-v2/components/cell/Boolean.vue
  4. 29
      packages/nc-gui-v2/components/cell/Currency.vue
  5. 14
      packages/nc-gui-v2/components/cell/Date.vue
  6. 18
      packages/nc-gui-v2/components/cell/DateTime.vue
  7. 32
      packages/nc-gui-v2/components/cell/Duration.vue
  8. 16
      packages/nc-gui-v2/components/cell/Email.vue
  9. 5
      packages/nc-gui-v2/components/cell/Enum.vue
  10. 12
      packages/nc-gui-v2/components/cell/Json.vue
  11. 4
      packages/nc-gui-v2/components/cell/SetList.vue
  12. 14
      packages/nc-gui-v2/components/cell/Time.vue
  13. 17
      packages/nc-gui-v2/components/cell/Url.vue
  14. 2
      packages/nc-gui-v2/components/dashboard/TabView.vue
  15. 2
      packages/nc-gui-v2/components/dashboard/TreeView.vue
  16. 35
      packages/nc-gui-v2/components/editable-cell/Boolean.vue
  17. 35
      packages/nc-gui-v2/components/editable-cell/DatePicker.vue
  18. 73
      packages/nc-gui-v2/components/editable-cell/DateTimePickerCell.vue
  19. 55
      packages/nc-gui-v2/components/editable-cell/DurationCell.vue
  20. 515
      packages/nc-gui-v2/components/editable-cell/EditableAttachmentCell.vue
  21. 27
      packages/nc-gui-v2/components/editable-cell/EditableUrlCell.vue
  22. 88
      packages/nc-gui-v2/components/editable-cell/EnumListEditableCell.vue
  23. 46
      packages/nc-gui-v2/components/editable-cell/EnumRadioEditableCell.vue
  24. 16
      packages/nc-gui-v2/components/editable-cell/FloatCell.vue
  25. 16
      packages/nc-gui-v2/components/editable-cell/IntegerCell.vue
  26. 102
      packages/nc-gui-v2/components/editable-cell/JsonEditableCell.vue
  27. 59
      packages/nc-gui-v2/components/editable-cell/RatingCell.vue
  28. 50
      packages/nc-gui-v2/components/editable-cell/SetListCheckboxCell.vue
  29. 97
      packages/nc-gui-v2/components/editable-cell/SetListEditableCell.vue
  30. 34
      packages/nc-gui-v2/components/editable-cell/TextAreaCell.vue
  31. 59
      packages/nc-gui-v2/components/editable-cell/TextAreaCellOld.vue
  32. 19
      packages/nc-gui-v2/components/editable-cell/TextCell.vue
  33. 39
      packages/nc-gui-v2/components/editable-cell/TimePickerCell.vue
  34. 10
      packages/nc-gui-v2/components/smartsheet/Cell.vue
  35. 82
      packages/nc-gui-v2/components/smartsheet/EditableCell.vue
  36. 53
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  37. 10
      packages/nc-gui-v2/components/tabs/Smartsheet.vue
  38. 66
      packages/nc-gui-v2/composables/useColumn.ts
  39. 2
      packages/nc-gui-v2/composables/useMetas.ts
  40. 24
      packages/nc-gui-v2/composables/useProject.ts
  41. 53
      packages/nc-gui-v2/composables/useViewData.ts
  42. 37
      packages/nc-gui-v2/pages/nc/[projectId].vue
  43. 100
      packages/nc-gui-v2/pages/projects/create-external.vue
  44. 11
      packages/nc-gui-v2/pages/projects/create.vue

1
packages/nc-gui-v2/app.vue

@ -1,4 +1,3 @@
<template>
<NuxtPage />
</template>

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

@ -1,3 +1,27 @@
<script>
import { isImage } from '@/components/project/spreadsheet/helpers/imageExt'
export default {
name: 'AttachmentCell',
props: ['value', 'column'],
data: () => ({
dragOver: false,
}),
computed: {
localState() {
try {
return JSON.parse(this.value) || []
} catch (e) {
return []
}
},
},
methods: {
isImage,
},
}
</script>
<template>
<div
class="d-flex align-center img-container d-100 h-100"
@ -11,9 +35,7 @@
>
<div v-if="dragOver" class="drop-overlay">
<div>
<v-icon small>
mdi-cloud-upload-outline
</v-icon>
<v-icon small> mdi-cloud-upload-outline </v-icon>
<span class="caption font-weight-bold">Drop here</span>
</div>
</div>
@ -21,14 +43,12 @@
<div v-for="item in localState" :key="item.title" class="thumbnail d-flex align-center justify-center">
<v-lazy class="d-flex align-center justify-center">
<v-tooltip bottom>
<template #activator="{on}">
<img v-if="isImage(item.title)" alt="#" :src="item.url" v-on="on">
<template #activator="{ on }">
<img v-if="isImage(item.title)" alt="#" :src="item.url" v-on="on" />
<v-icon v-else-if="item.icon" size="33" v-on="on">
{{ item.icon }}
</v-icon>
<v-icon v-else size="33" v-on="on">
mdi-file
</v-icon>
<v-icon v-else size="33" v-on="on"> mdi-file </v-icon>
</template>
<span>{{ item.title }}</span>
</v-tooltip>
@ -38,30 +58,6 @@
</div>
</template>
<script>
import { isImage } from '@/components/project/spreadsheet/helpers/imageExt'
export default {
name: 'AttachmentCell',
props: ['value', 'column'],
data: () => ({
dragOver: false
}),
computed: {
localState() {
try {
return JSON.parse(this.value) || []
} catch (e) {
return []
}
}
},
methods: {
isImage
}
}
</script>
<style scoped>
.img-container {
margin: 0 -2px;
@ -95,7 +91,6 @@ export default {
align-items: center;
pointer-events: none;
}
</style>
<!--
/**

2
packages/nc-gui-v2/components/cell/Boolean.vue

@ -6,6 +6,6 @@ const value = inject<boolean | number>('value')
<template>
<div class="d-flex align-center">
<input v-model="value" type="checkbox" :disabled="true">
<input v-model="value" type="checkbox" :disabled="true" />
</div>
</template>

29
packages/nc-gui-v2/components/cell/Currency.vue

@ -1,23 +1,19 @@
<template>
<a v-if="value">{{ currency }}</a>
<span v-else />
</template>
<script>
export default {
name: 'CurrencyCell',
props: {
column: Object,
value: [String, Number]
value: [String, Number],
},
computed: {
currency() {
try {
return isNaN(this.value)
? this.value
: new Intl.NumberFormat(this.currencyMeta.currency_locale || 'en-US',
{ style: 'currency', currency: this.currencyMeta.currency_code || 'USD' })
.format(this.value)
: new Intl.NumberFormat(this.currencyMeta.currency_locale || 'en-US', {
style: 'currency',
currency: this.currencyMeta.currency_code || 'USD',
}).format(this.value)
} catch (e) {
return this.value
}
@ -26,15 +22,16 @@ export default {
return {
currency_locale: 'en-US',
currency_code: 'USD',
...(this.column && this.column.meta
? this.column.meta
: {})
...(this.column && this.column.meta ? this.column.meta : {}),
}
}
}
},
},
}
</script>
<style scoped>
<template>
<a v-if="value">{{ currency }}</a>
<span v-else />
</template>
</style>
<style scoped></style>

14
packages/nc-gui-v2/components/cell/Date.vue

@ -1,7 +1,3 @@
<template>
<span>{{ date }}</span>
</template>
<script>
import dayjs from 'dayjs'
@ -11,11 +7,13 @@ export default {
computed: {
date() {
return (/^\d+$/.test(this.value) ? dayjs(+this.value) : dayjs(this.value)).format('YYYY-MM-DD')
}
}
},
},
}
</script>
<style scoped>
<template>
<span>{{ date }}</span>
</template>
</style>
<style scoped></style>

18
packages/nc-gui-v2/components/cell/DateTime.vue

@ -1,7 +1,3 @@
<template>
<span>{{ dateTime }}</span>
</template>
<script>
import dayjs from 'dayjs'
@ -10,12 +6,16 @@ export default {
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')
}
}
return !this.value
? this.value
: (/^\d+$/.test(this.value) ? dayjs(+this.value) : dayjs(this.value)).format('YYYY-MM-DD HH:mm')
},
},
}
</script>
<style scoped>
<template>
<span>{{ dateTime }}</span>
</template>
</style>
<style scoped></style>

32
packages/nc-gui-v2/components/cell/Duration.vue

@ -1,48 +1,42 @@
<template>
<input
v-model="localValue"
:placeholder="durationPlaceholder"
readonly
>
</template>
<script>
import { durationOptions, convertMS2Duration } from '~/helpers/durationHelper'
import { convertMS2Duration, durationOptions } from '~/helpers/durationHelper'
export default {
name: 'DurationCell',
props: {
column: Object,
value: [String, Number]
value: [String, Number],
},
data: () => ({
showWarningMessage: false,
localValue: null
localValue: null,
}),
computed: {
durationPlaceholder() {
return durationOptions[this.column?.meta?.duration || 0].title
}
},
},
watch: {
'column.meta.duration'(newValue, oldValue) {
'column.meta.duration': function (newValue, oldValue) {
if (oldValue !== newValue) {
this.localValue = convertMS2Duration(this.value, newValue)
}
},
value(val, oldVal) {
this.localValue = convertMS2Duration(val !== oldVal && (!val && val !== 0) ? oldVal : val, this.column?.meta?.duration || 0)
}
'value': function (val, oldVal) {
this.localValue = convertMS2Duration(val !== oldVal && !val && val !== 0 ? oldVal : val, this.column?.meta?.duration || 0)
},
},
created() {
this.localValue = convertMS2Duration(this.value, this.column?.meta?.duration || 0)
}
},
}
</script>
<style scoped>
<template>
<input v-model="localValue" :placeholder="durationPlaceholder" readonly />
</template>
</style>
<style scoped></style>
<!--
/**

16
packages/nc-gui-v2/components/cell/Email.vue

@ -1,8 +1,3 @@
<template>
<a v-if="isEmail" :href="`mailto:${value}`" target="_blank">{{ value }}</a>
<span v-else>{{ value }}</span>
</template>
<script>
import { isEmail } from '~/helpers'
@ -12,11 +7,14 @@ export default {
computed: {
isEmail() {
return isEmail(this.value || '')
}
}
},
},
}
</script>
<style scoped>
<template>
<a v-if="isEmail" :href="`mailto:${value}`" target="_blank">{{ value }}</a>
<span v-else>{{ value }}</span>
</template>
</style>
<style scoped></style>

5
packages/nc-gui-v2/components/cell/Enum.vue

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import { inject } from 'vue'
import { enumColor } from "~/utils/colorsUtils";
import { enumColor } from '~/utils/colorsUtils'
const colors = enumColor.light
@ -18,7 +18,8 @@ const column = inject<ColumnType>('column')
background: colors[v],
}"
class="set-item ma-1 py-1 px-3"
>{{ v }}</span>
>{{ v }}</span
>
</div>
</template>

12
packages/nc-gui-v2/components/cell/Json.vue

@ -1,14 +1,12 @@
<template>
<pre class="text-left caption">{{ value }}</pre>
</template>
<script>
export default {
name: 'JsonCell',
props: ['value']
props: ['value'],
}
</script>
<style scoped>
<template>
<pre class="text-left caption">{{ value }}</pre>
</template>
</style>
<style scoped></style>

4
packages/nc-gui-v2/components/cell/SetList.vue

@ -8,12 +8,12 @@ export default {
computed: {
setValues() {
if (this.column && this.column.dtxp)
return this.column.dtxp.split(',').map(v => v.replace(/\\'/g, '\'').replace(/^'|'$/g, ''))
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, '')) : []
return this.value ? this.value.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) : []
},
},
}

14
packages/nc-gui-v2/components/cell/Time.vue

@ -1,7 +1,3 @@
<template>
<span>{{ time }}</span>
</template>
<script>
export default {
name: 'TimeCell',
@ -9,11 +5,13 @@ export default {
computed: {
time() {
return typeof this.value === 'string' ? this.value.replace(/(\d)T(?=\d)/, '$1 ') : this.value
}
}
},
},
}
</script>
<style scoped>
<template>
<span>{{ time }}</span>
</template>
</style>
<style scoped></style>

17
packages/nc-gui-v2/components/cell/Url.vue

@ -1,10 +1,4 @@
<template>
<a v-if="isValid" :href="value" target="_blank">{{ value }}</a>
<span v-else>{{ value }}</span>
</template>
<script>
import { isValidURL } from '~/helpers'
export default {
@ -13,11 +7,14 @@ export default {
computed: {
isValid() {
return this.value && isValidURL(this.value)
}
}
},
},
}
</script>
<style scoped>
<template>
<a v-if="isValid" :href="value" target="_blank">{{ value }}</a>
<span v-else>{{ value }}</span>
</template>
</style>
<style scoped></style>

2
packages/nc-gui-v2/components/dashboard/TabView.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import useTabs from '~/composables/useTabs'
import useTabs from '~/composables/useTabs'
const { tabs, activeTab } = useTabs()
</script>

2
packages/nc-gui-v2/components/dashboard/TreeView.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import useProject from '~/composables/useProject'
import useTabs from '~/composables/useTabs'
import useTabs from '~/composables/useTabs'
const { tables } = useProject()
const { addTab } = useTabs()

35
packages/nc-gui-v2/components/editable-cell/Boolean.vue

@ -1,11 +1,3 @@
<template>
<div class="d-flex align-center " :class="{'justify-center':!isForm,'nc-cell-hover-show': !localState}">
<v-icon small :color="checkboxMeta.color" @click="toggle">
{{ localState ? checkedIcon :uncheckedIcon }}
</v-icon>
</div>
</template>
<script>
export default {
name: 'BooleanCell',
@ -13,10 +5,9 @@ export default {
column: Object,
value: [String, Number, Boolean],
isForm: Boolean,
readOnly: Boolean
readOnly: Boolean,
},
computed: {
checkedIcon() {
return (this.checkboxMeta && this.checkboxMeta.icon && this.checkboxMeta.icon.checked) || 'mdi-check-bold'
},
@ -29,7 +20,7 @@ export default {
},
set(val) {
this.$emit('input', val)
}
},
},
parentListeners() {
const $listeners = {}
@ -39,26 +30,30 @@ export default {
return {
icon: {
checked: 'mdi-check-circle-outline',
unchecked: 'mdi-checkbox-blank-circle-outline'
unchecked: 'mdi-checkbox-blank-circle-outline',
},
color: 'primary',
...(this.column && this.column.meta
? this.column.meta
: {})
...(this.column && this.column.meta ? this.column.meta : {}),
}
}
},
},
methods: {
toggle() {
this.localState = !this.localState
}
}
},
},
}
</script>
<style scoped>
<template>
<div class="d-flex align-center" :class="{ 'justify-center': !isForm, 'nc-cell-hover-show': !localState }">
<v-icon small :color="checkboxMeta.color" @click="toggle">
{{ localState ? checkedIcon : uncheckedIcon }}
</v-icon>
</div>
</template>
</style>
<style scoped></style>
<!--
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd

35
packages/nc-gui-v2/components/editable-cell/DatePicker.vue

@ -1,29 +1,17 @@
<template>
<v-menu>
<template #activator="{on}">
<input :value="date" class="value" v-on="on">
</template>
<v-date-picker
v-model="localState"
flat
@click.native.stop
v-on="parentListeners"
/>
</v-menu>
</template>
<script>
import dayjs from 'dayjs'
export default {
name: 'DatePickerCell',
props: {
value: [String, Date]
value: [String, Date],
},
computed: {
localState: {
get() {
if (!this.value || !dayjs(this.value).isValid()) { return undefined }
if (!this.value || !dayjs(this.value).isValid()) {
return undefined
}
return (/^\d+$/.test(this.value) ? dayjs(+this.value) : dayjs(this.value)).format('YYYY-MM-DD')
},
@ -31,7 +19,7 @@ export default {
if (dayjs(val).isValid()) {
this.$emit('input', val && dayjs(val).format('YYYY-MM-DD'))
}
}
},
},
date() {
if (!this.value || this.localState) {
@ -50,16 +38,25 @@ export default {
}
return $listeners
}
},
},
mounted() {
if (this.$el && this.$el.$el) {
this.$el.$el.focus()
}
}
},
}
</script>
<template>
<v-menu>
<template #activator="{ on }">
<input :value="date" class="value" v-on="on" />
</template>
<v-date-picker v-model="localState" flat @click.native.stop v-on="parentListeners" />
</v-menu>
</template>
<style scoped>
.value {
width: 100%;

73
packages/nc-gui-v2/components/editable-cell/DateTimePickerCell.vue

@ -1,32 +1,4 @@
<template>
<div>
<div v-show="!showMessage">
<v-datetime-picker
ref="picker"
v-model="localState"
class="caption xc-date-time-picker"
:text-field-props="{
class:'caption mt-0 pt-0',
flat:true,
solo:true,
dense:true,
hideDetails:true
}"
:time-picker-props="{
format:'24hr'
}"
v-on="parentListeners"
/>
</div>
<div v-show="showMessage" class="edit-warning" @dblclick="$refs.picker.display = true">
<!-- TODO: i18n -->
ERR: Couldn't parse {{ this.value }}
</div>
</div>
</template>
<script>
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
@ -35,10 +7,11 @@ dayjs.extend(utc)
export default {
name: 'DateTimePickerCell',
props: {
value: [String, Date, Number], ignoreFocus: Boolean
value: [String, Date, Number],
ignoreFocus: Boolean,
},
data: () => ({
showMessage: false
showMessage: false,
}),
computed: {
isMysql() {
@ -49,7 +22,7 @@ export default {
if (!this.value) {
return this.value
}
const d = (/^\d+$/.test(this.value) ? dayjs(+this.value) : dayjs(this.value))
const d = /^\d+$/.test(this.value) ? dayjs(+this.value) : dayjs(this.value)
if (d.isValid()) {
this.showMessage = false
return d.format('YYYY-MM-DD HH:mm')
@ -63,7 +36,7 @@ export default {
} else {
this.$emit('input', value && dayjs(value).format('YYYY-MM-DD HH:mm:ssZ'))
}
}
},
},
parentListeners() {
const $listeners = {}
@ -76,7 +49,7 @@ export default {
}
return $listeners
}
},
},
mounted() {
// listen dialog click:outside event and save on close
@ -89,12 +62,40 @@ export default {
if (!this.ignoreFocus) {
this.$refs.picker.display = true
}
}
},
}
</script>
<template>
<div>
<div v-show="!showMessage">
<v-datetime-picker
ref="picker"
v-model="localState"
class="caption xc-date-time-picker"
:text-field-props="{
class: 'caption mt-0 pt-0',
flat: true,
solo: true,
dense: true,
hideDetails: true,
}"
:time-picker-props="{
format: '24hr',
}"
v-on="parentListeners"
/>
</div>
<div v-show="showMessage" class="edit-warning" @dblclick="$refs.picker.display = true">
<!-- TODO: i18n -->
ERR: Couldn't parse {{ value }}
</div>
</div>
</template>
<style scoped>
/deep/ .v-input, /deep/ .v-text-field {
:deep(.v-input),
:deep(.v-text-field) {
margin-top: 0 !important;
padding-top: 0 !important;
font-size: inherit !important;
@ -103,7 +104,7 @@ export default {
.edit-warning {
padding: 10px;
text-align: left;
color: #E65100;
color: #e65100;
}
</style>
<!--

55
packages/nc-gui-v2/components/editable-cell/DurationCell.vue

@ -1,30 +1,12 @@
<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>
<script>
import { durationOptions, convertMS2Duration, convertDurationToSeconds } from '~/helpers/durationHelper'
import { convertDurationToSeconds, convertMS2Duration, durationOptions } from '~/helpers/durationHelper'
export default {
name: 'DurationCell',
props: {
column: Object,
value: [Number, String],
readOnly: Boolean
readOnly: Boolean,
},
data: () => ({
// flag to determine to show warning message or not
@ -32,7 +14,7 @@ export default {
// duration in milliseconds
durationInMS: null,
// check if the cell is edited or not
isEdited: false
isEdited: false,
}),
computed: {
localState: {
@ -45,7 +27,7 @@ export default {
if (res._isValid) {
this.durationInMS = res._sec
}
}
},
},
durationPlaceholder() {
return durationOptions[this.durationType].title
@ -64,7 +46,7 @@ export default {
}
return $listeners
}
},
},
mounted() {
window.addEventListener('keypress', (_) => {
@ -76,7 +58,7 @@ export default {
methods: {
checkDurationFormat(evt) {
evt = evt || window.event
const charCode = (evt.which) ? evt.which : evt.keyCode
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
@ -96,13 +78,30 @@ export default {
this.$emit('input', this.durationInMS)
}
this.isEdited = false
}
}
},
},
}
</script>
<style scoped>
<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;
}
@ -110,7 +109,7 @@ export default {
.duration-warning {
text-align: left;
margin-top: 10px;
color: #E65100;
color: #e65100;
}
</style>

515
packages/nc-gui-v2/components/editable-cell/EditableAttachmentCell.vue

@ -1,3 +1,178 @@
<script>
import FileSaver from 'file-saver'
import draggable from 'vuedraggable'
import { isImage } from '@/components/project/spreadsheet/helpers/imageExt'
export default {
name: 'EditableAttachmentCell',
components: { Draggable: draggable },
props: ['dbAlias', 'value', 'active', 'isLocked', 'meta', 'column', 'isPublicGrid', 'isForm', 'isPublicForm', 'viewId'],
data: () => ({
carousel: null,
uploading: false,
localState: '',
dialog: false,
showImage: false,
selectedImage: null,
dragOver: false,
localFilesState: [],
urlString: '',
}),
watch: {
value(val, prev) {
try {
this.localState = ((typeof val === 'string' && val !== prev ? JSON.parse(val) : val) || []).filter(Boolean)
} catch (e) {
this.localState = []
}
},
},
created() {
try {
this.localState = ((typeof this.value === 'string' ? JSON.parse(this.value) : this.value) || []).filter(Boolean)
} catch (e) {
this.localState = []
}
document.addEventListener('keydown', this.onArrowDown)
},
beforeUnmount() {
document.removeEventListener('keydown', this.onArrowDown)
},
mounted() {},
methods: {
async uploadByUrl() {
const data = await this.$api.storage.uploadByUrl(
{
path: ['noco', this.projectName, this.meta.title, this.column.title].join('/'),
},
[
{
url: this.urlString,
},
],
)
this.localState.push(...data)
},
openUrl(url, target) {
window.open(url, target)
},
isImage,
hideIfVisible() {
if (this.showImage) {
this.showImage = false
}
},
selectImage(selectedImage, i) {
this.carousel = i
this.selectedImage = selectedImage
this.showImage = true
},
addFile() {
if (!this.isLocked) {
this.$refs.file.click()
}
},
async onFileSelection() {
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
}
this.uploading = true
for (const file of this.$refs.file.files) {
try {
const data = await this.$api.storage.upload(
{
path: ['noco', this.projectName, this.meta.title, this.column.title].join('/'),
},
{
files: file,
json: '{}',
},
)
this.localState.push(...data)
} catch (e) {
this.$toast.error(e.message || 'Some internal error occurred').goAway(3000)
this.uploading = false
return
}
}
this.uploading = false
this.$emit('input', JSON.stringify(this.localState))
this.$emit('update')
},
onOrderUpdate() {
this.$emit('input', JSON.stringify(this.localState))
this.$emit('update')
},
removeItem(i) {
if (this.isPublicForm) {
this.localFilesState.splice(i, 1)
this.$emit(
'input',
this.localFilesState.map((f) => f.file),
)
} else {
this.localState.splice(i, 1)
this.$emit('input', JSON.stringify(this.localState))
}
this.$emit('update')
},
downloadItem(item) {
FileSaver.saveAs(item.url || item.data, item.title)
},
onArrowDown(e) {
if (!this.showImage) {
return
}
e = e || window.event
// eslint-disable-next-line eqeqeq
if (e.keyCode == '37') {
this.carousel = (this.carousel || this.localState.length) - 1
// eslint-disable-next-line eqeqeq
} else if (e.keyCode == '39') {
this.carousel = ++this.carousel % this.localState.length
// eslint-disable-next-line eqeqeq
} else if (e.keyCode == '27') {
this.hideIfVisible()
}
},
async onFileDrop(e) {
this.dragOver = false
this.$refs.file.files = e.dataTransfer.files
await this.onFileSelection()
},
},
}
</script>
<template>
<div
class="main h-100"
@ -10,9 +185,7 @@
>
<div v-show="(isForm || _isUIAllowed('tableAttachment')) && dragOver" class="drop-overlay">
<div>
<v-icon small>
mdi-cloud-upload-outline
</v-icon>
<v-icon small> mdi-cloud-upload-outline </v-icon>
<span class="caption font-weight-bold">Drop here</span>
</div>
</div>
@ -20,12 +193,12 @@
<div class="d-flex align-center img-container">
<div class="d-flex no-overflow">
<div
v-for="(item,i) in (isPublicForm ? localFilesState : localState)"
v-for="(item, i) in isPublicForm ? localFilesState : localState"
:key="item.url || item.title"
class="thumbnail align-center justify-center d-flex"
>
<v-tooltip bottom>
<template #activator="{on}">
<template #activator="{ on }">
<v-img
v-if="isImage(item.title, item.mimetype)"
lazy-src="https://via.placeholder.com/60.png?text=Loading..."
@ -37,24 +210,13 @@
@click="selectImage(item.url || item.data, i)"
>
<template #placeholder>
<v-skeleton-loader
type="image"
:height="active ? 33 : 22"
:width="active ? 33 : 22"
/>
<v-skeleton-loader type="image" :height="active ? 33 : 22" :width="active ? 33 : 22" />
</template>
</v-img>
<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-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')">
<v-icon v-else :size="active ? 33 : 22" v-on="on" @click="openUrl(item.url || item.data, '_blank')">
mdi-file
</v-icon>
</template>
@ -63,75 +225,47 @@
</div>
</div>
<div
v-if="isForm || active && !isPublicGrid && !isLocked"
v-if="isForm || (active && !isPublicGrid && !isLocked)"
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="">
mdi-plus
</v-icon>
<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=""> mdi-plus </v-icon>
Attachment
</v-btn>
<v-icon
v-else-if="_isUIAllowed('tableAttachment')"
v-show="active"
small
color="primary nc-attachment-add-icon"
>
<v-icon v-else-if="_isUIAllowed('tableAttachment')" v-show="active" small color="primary nc-attachment-add-icon">
mdi-plus
</v-icon>
</div>
<v-spacer />
<v-icon class="expand-icon mr-1" x-small color="primary" @click.stop="dialog = true">
mdi-arrow-expand
</v-icon>
<input ref="file" type="file" multiple class="d-none" @change="onFileSelection">
<v-icon class="expand-icon mr-1" x-small color="primary" @click.stop="dialog = true"> mdi-arrow-expand </v-icon>
<input ref="file" type="file" multiple class="d-none" @change="onFileSelection" />
</div>
<v-dialog
v-if="dialog"
v-model="dialog"
width="800"
>
<v-dialog v-if="dialog" v-model="dialog" width="800">
<v-card class="h-100 images-modal">
<v-card-text class="h-100 backgroundColor">
<div class="d-flex mx-2">
<v-btn
v-if="(isForm || _isUIAllowed('tableAttachment')) && !isPublicGrid && !isLocked"
small
class="my-4 "
class="my-4"
:loading="uploading"
@click="addFile"
>
<v-icon small class="mr-2">
mdi-link-variant
</v-icon>
<v-icon small class="mr-2"> mdi-link-variant </v-icon>
<span class="caption">Attach File</span>
</v-btn>
<!-- <v-text-field v-model="urlString" @keypress.enter="uploadByUrl" />-->
<!-- <v-text-field v-model="urlString" @keypress.enter="uploadByUrl" /> -->
</div>
<div class="d-flex flex-wrap h-100">
<v-container fluid style="max-height:calc(90vh - 80px);overflow-y: auto">
<draggable
v-model="localState"
class="row"
@update="onOrderUpdate"
>
<v-col v-for="(item,i) in (isPublicForm ? localFilesState : localState)" :key="i" cols="4">
<v-container fluid style="max-height: calc(90vh - 80px); overflow-y: auto">
<Draggable v-model="localState" class="row" @update="onOrderUpdate">
<v-col v-for="(item, i) in isPublicForm ? localFilesState : localState" :key="i" cols="4">
<v-card
class="modal-thumbnail-card align-center justify-center d-flex"
height="200px"
@ -145,33 +279,27 @@
>
mdi-close-circle
</v-icon>
<v-icon color="grey" class="download-icon" @click.stop="downloadItem(item,i)">
mdi-download
</v-icon>
<div class="pa-2 d-flex align-center" style="height:200px">
<v-icon color="grey" class="download-icon" @click.stop="downloadItem(item, i)"> mdi-download </v-icon>
<div class="pa-2 d-flex align-center" style="height: 200px">
<img
v-if="isImage(item.title, item.mimetype)"
style="max-height: 100%;max-width: 100%"
style="max-height: 100%; max-width: 100%"
alt="#"
:src="item.url || item.data"
@click="selectImage(item.url,i)"
>
@click="selectImage(item.url, i)"
/>
<v-icon v-else-if="item.icon" size="33" @click="openUrl(item.url || item.data,'_blank')">
{{
item.icon
}}
</v-icon>
<v-icon v-else size="33" @click="openUrl(item.url || item.data,'_blank')">
mdi-file
<v-icon v-else-if="item.icon" size="33" @click="openUrl(item.url || item.data, '_blank')">
{{ item.icon }}
</v-icon>
<v-icon v-else size="33" @click="openUrl(item.url || item.data, '_blank')"> mdi-file </v-icon>
</div>
</v-card>
<p class="caption mt-2 modal-title" :title="item.title">
{{ item.title }}
</p>
</v-col>
</draggable>
</Draggable>
</v-container>
</div>
</v-card-text>
@ -182,29 +310,22 @@
<div v-click-outside="hideIfVisible" class="image-overlay-container">
<template v-if="showImage && selectedImage">
<v-carousel v-model="carousel" height="calc(100vh - 100px)" hide-delimiters>
<v-carousel-item
v-for="(item,i) in (isPublicForm ? localFilesState : localState)"
:key="i"
>
<div class="mx-auto d-flex flex-column justify-center align-center" style="min-height:100px">
<v-carousel-item v-for="(item, i) in isPublicForm ? localFilesState : localState" :key="i">
<div class="mx-auto d-flex flex-column justify-center align-center" style="min-height: 100px">
<p class="title text-center">
{{ item.title }}
<v-icon class="ml-3" color="grey" @click.stop="downloadItem(item,i)">
mdi-download
</v-icon>
<v-icon class="ml-3" color="grey" @click.stop="downloadItem(item, i)"> mdi-download </v-icon>
</p>
<div style="width:90vh;height:calc(100vh - 150px)" class="d-flex align-center justify-center">
<div style="width: 90vh; height: calc(100vh - 150px)" class="d-flex align-center justify-center">
<img
v-if="isImage(item.title, item.mimetype)"
style="max-width:90vh;max-height:calc(100vh - 100px)"
style="max-width: 90vh; max-height: calc(100vh - 100px)"
:src="item.url || item.data"
>
/>
<v-icon v-else-if="item.icon" size="55">
{{ item.icon }}
</v-icon>
<v-icon v-else size="55">
mdi-file
</v-icon>
<v-icon v-else size="55"> mdi-file </v-icon>
</div>
</div>
</v-carousel-item>
@ -217,210 +338,35 @@
height="80px"
style="background: transparent"
>
<v-slide-group
multiple
show-arrows
>
<v-slide-item
v-for="(item,i) in (isPublicForm ? localFilesState : localState)"
:key="i"
>
<v-slide-group multiple show-arrows>
<v-slide-item v-for="(item, i) in isPublicForm ? localFilesState : localState" :key="i">
<v-card
:key="i"
class="ma-2 pa-2 d-flex align-center justify-center overlay-thumbnail"
:class="{active: carousel === i}"
:class="{ active: carousel === i }"
width="48"
height="48"
@click="carousel = i"
>
<img
v-if="isImage(item.title, item.mimetype)"
style="max-width:100%;max-height:100%"
style="max-width: 100%; max-height: 100%"
:src="item.url || item.data"
>
/>
<v-icon v-else-if="item.icon" size="48">
{{ item.icon }}
</v-icon>
<v-icon v-else size="48">
mdi-file
</v-icon>
<v-icon v-else size="48"> mdi-file </v-icon>
</v-card>
</v-slide-item>
</v-slide-group>
</v-sheet>
<v-icon x-large class="close-icon" @click="showImage=false">
mdi-close-circle
</v-icon>
<v-icon x-large class="close-icon" @click="showImage = false"> mdi-close-circle </v-icon>
</div>
</v-overlay>
</div>
</template>
<script>
import FileSaver from 'file-saver'
import draggable from 'vuedraggable'
import { isImage } from '@/components/project/spreadsheet/helpers/imageExt'
export default {
name: 'EditableAttachmentCell',
components: { draggable },
props: ['dbAlias', 'value', 'active', 'isLocked', 'meta', 'column', 'isPublicGrid', 'isForm', 'isPublicForm', 'viewId'],
data: () => ({
carousel: null,
uploading: false,
localState: '',
dialog: false,
showImage: false,
selectedImage: null,
dragOver: false,
localFilesState: [],
urlString: ''
}),
watch: {
value(val, prev) {
try {
this.localState = ((typeof val === 'string' && val !== prev ? JSON.parse(val) : val) || []).filter(Boolean)
} catch (e) {
this.localState = []
}
}
},
created() {
try {
this.localState = ((typeof this.value === 'string' ? JSON.parse(this.value) : this.value) || []).filter(Boolean)
} catch (e) {
this.localState = []
}
document.addEventListener('keydown', this.onArrowDown)
},
beforeDestroy() {
document.removeEventListener('keydown', this.onArrowDown)
},
mounted() {
},
methods: {
async uploadByUrl() {
const data = await this.$api.storage.uploadByUrl(
{
path: ['noco', this.projectName, this.meta.title, this.column.title].join('/')
},
[{
url: this.urlString
}]
)
this.localState.push(...data)
},
openUrl(url, target) {
window.open(url, target)
},
isImage,
hideIfVisible() {
if (this.showImage) {
this.showImage = false
}
},
selectImage(selectedImage, i) {
this.carousel = i
this.selectedImage = selectedImage
this.showImage = true
},
addFile() {
if (!this.isLocked) {
this.$refs.file.click()
}
},
async onFileSelection() {
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
}
this.uploading = true
for (const file of this.$refs.file.files) {
try {
const data = await this.$api.storage.upload(
{
path: ['noco', this.projectName, this.meta.title, this.column.title].join('/')
}, {
files: file,
json: '{}'
}
)
this.localState.push(...data)
} catch (e) {
this.$toast.error((e.message) || 'Some internal error occurred').goAway(3000)
this.uploading = false
return
}
}
this.uploading = false
this.$emit('input', JSON.stringify(this.localState))
this.$emit('update')
},
onOrderUpdate() {
this.$emit('input', JSON.stringify(this.localState))
this.$emit('update')
},
removeItem(i) {
if (this.isPublicForm) {
this.localFilesState.splice(i, 1)
this.$emit('input', this.localFilesState.map(f => f.file))
} else {
this.localState.splice(i, 1)
this.$emit('input', JSON.stringify(this.localState))
}
this.$emit('update')
},
downloadItem(item) {
FileSaver.saveAs(item.url || item.data, item.title)
},
onArrowDown(e) {
if (!this.showImage) {
return
}
e = e || window.event
// eslint-disable-next-line eqeqeq
if (e.keyCode == '37') {
this.carousel = (this.carousel || this.localState.length) - 1
// eslint-disable-next-line eqeqeq
} else if (e.keyCode == '39') {
this.carousel = ++this.carousel % this.localState.length
// eslint-disable-next-line eqeqeq
} else if (e.keyCode == '27') {
this.hideIfVisible()
}
},
async onFileDrop(e) {
this.dragOver = false
this.$refs.file.files = e.dataTransfer.files
await this.onFileSelection()
}
}
}
</script>
<style scoped lang="scss">
.img-container {
margin: 0 -2px;
@ -431,7 +377,7 @@ export default {
}
.add {
transition: .2s background-color;
transition: 0.2s background-color;
/*background-color: #666666ee;*/
border-radius: 4px;
height: 33px;
@ -464,7 +410,7 @@ export default {
margin-left: 8px;
border-radius: 2px;
/*opacity: 0;*/
transition: .3s background-color;
transition: 0.3s background-color;
}
.expand-icon:hover {
@ -476,7 +422,6 @@ export default {
height: 50px;
max-width: 100%;
border-radius: 4px;
}
.modal-thumbnail {
@ -487,21 +432,20 @@ export default {
.remove-icon {
position: absolute;
top: 5px;
right: 5px
right: 5px;
}
.modal-thumbnail-card {
.download-icon {
position: absolute;
bottom: 5px;
right: 5px;
opacity: 0;
transition: .4s opacity;
transition: 0.4s opacity;
}
&:hover .download-icon {
opacity: 1
opacity: 1;
}
}
@ -514,12 +458,12 @@ export default {
.image-overlay-container .close-icon {
position: fixed;
top: 15px;
right: 15px
right: 15px;
}
.overlay-thumbnail {
transition: .4s transform, .4s opacity;
opacity: .5;
transition: 0.4s transform, 0.4s opacity;
opacity: 0.5;
}
.overlay-thumbnail.active {
@ -539,7 +483,7 @@ export default {
}
.modal-thumbnail-card {
transition: .4s transform;
transition: 0.4s transform;
}
.modal-thumbnail-card:hover {
@ -564,13 +508,12 @@ export default {
.expand-icon {
opacity: 0;
transition: .4s opacity;
transition: 0.4s opacity;
}
.main:hover .expand-icon {
opacity: 1;
}
</style>
<!--
/**

27
packages/nc-gui-v2/components/editable-cell/EditableUrlCell.vue

@ -1,7 +1,3 @@
<template>
<input v-model="localState" v-on="parentListeners">
</template>
<script>
import { isValidURL } from '@/helpers'
@ -9,7 +5,7 @@ export default {
name: 'EditableUrlCell',
props: {
value: String,
column: Object
column: Object,
},
computed: {
localState: {
@ -17,12 +13,10 @@ export default {
return this.value
},
set(val) {
if (!(
this.column &&
this.column.meta &&
this.column.meta.validate
) || isValidURL(val)) { this.$emit('input', val) }
}
if (!(this.column && this.column.meta && this.column.meta.validate) || isValidURL(val)) {
this.$emit('input', val)
}
},
},
parentListeners() {
const $listeners = {}
@ -39,16 +33,21 @@ export default {
}
return $listeners
}
},
},
mounted() {
this.$el.focus()
}
},
}
</script>
<template>
<input v-model="localState" v-on="parentListeners" />
</template>
<style scoped>
input, textarea {
input,
textarea {
width: 100%;
height: 100%;
color: var(--v-textColor-base);

88
packages/nc-gui-v2/components/editable-cell/EnumListEditableCell.vue

@ -1,40 +1,3 @@
<template>
<v-select
v-model="localState"
solo
dense
flat
:items="enumValues"
hide-details
class="mt-0"
:clearable="!column.rqd"
v-on="parentListeners"
>
<template #selection="{item}">
<div
class="d-100"
:class="{
'text-center' : !isForm
}"
>
<v-chip small :color="colors[enumValues.indexOf(item) % colors.length]" class="ma-1">
{{ item }}
</v-chip>
</div>
</template>
<template #item="{item}">
<v-chip small :color="colors[enumValues.indexOf(item) % colors.length]">
{{ item }}
</v-chip>
</template>
<template #append>
<v-icon small class="mt-1">
mdi-menu-down
</v-icon>
</template>
</v-select>
</template>
<script>
import colors from '@/mixins/colors'
@ -45,22 +8,20 @@ export default {
props: {
value: String,
column: Object,
isForm: Boolean
isForm: Boolean,
},
computed: {
localState: {
get() {
return this.value && this.value.replace(/\\'/g, '\'').replace(/^'|'$/g, '')
return this.value && this.value.replace(/\\'/g, "'").replace(/^'|'$/g, '')
},
set(val) {
this.$emit('input', val)
}
},
},
enumValues() {
if (this.column && this.column.dtxp) {
return this.column.dtxp
.split(',')
.map(v => v.replace(/\\'/g, '\'').replace(/^'|'$/g, ''))
return this.column.dtxp.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, ''))
}
return []
},
@ -75,7 +36,7 @@ export default {
}
return $listeners
}
},
},
mounted() {
// this.$el.focus();
@ -83,16 +44,51 @@ export default {
// event = document.createEvent('MouseEvents');
// event.initMouseEvent('mousedown', true, true, window);
// this.$el.dispatchEvent(event);
}
},
}
</script>
<template>
<v-select
v-model="localState"
solo
dense
flat
:items="enumValues"
hide-details
class="mt-0"
:clearable="!column.rqd"
v-on="parentListeners"
>
<template #selection="{ item }">
<div
class="d-100"
:class="{
'text-center': !isForm,
}"
>
<v-chip small :color="colors[enumValues.indexOf(item) % colors.length]" class="ma-1">
{{ item }}
</v-chip>
</div>
</template>
<template #item="{ item }">
<v-chip small :color="colors[enumValues.indexOf(item) % colors.length]">
{{ item }}
</v-chip>
</template>
<template #append>
<v-icon small class="mt-1"> mdi-menu-down </v-icon>
</template>
</v-select>
</template>
<style scoped lang="scss">
::v-deep {
.v-select {
min-width: 150px;
}
.v-input__slot{
.v-input__slot {
padding-right: 0 !important;
padding-left: 35px !important;
}

46
packages/nc-gui-v2/components/editable-cell/EnumRadioEditableCell.vue

@ -1,20 +1,3 @@
<template>
<div class="d-flex align-center">
<div>
<div v-for="(val,i) of enumValues" :key="val" class="item">
<input :id="`key-radio-${val}`" v-model="localState" type="radio" class="orange--text" :value="val">
<label
class="py-1 px-3 d-inline-block my-1 label"
:for="`key-radio-${val}`"
:style="{
background:colors[i % colors.length ]
}"
>{{ val }}</label>
</div>
</div>
</div>
</template>
<script>
import { enumColor as colors } from '@/components/project/spreadsheet/helpers/colors'
@ -22,7 +5,7 @@ export default {
name: 'EnumRadioEditableCell',
props: {
value: String,
column: Object
column: Object,
},
computed: {
colors() {
@ -35,11 +18,11 @@ export default {
set(val) {
this.$emit('input', val)
this.$emit('update')
}
},
},
enumValues() {
if (this.column && this.column.dtxp) {
return this.column.dtxp.split(',').map(v => v.replace(/^'|'$/g, ''))
return this.column.dtxp.split(',').map((v) => v.replace(/^'|'$/g, ''))
}
return []
},
@ -53,7 +36,7 @@ export default {
$listeners.focus = this.$listeners.focus
}
return $listeners
}
},
},
mounted() {
// this.$el.focus();
@ -61,12 +44,29 @@ export default {
// event = document.createEvent('MouseEvents');
// event.initMouseEvent('mousedown', true, true, window);
// this.$el.dispatchEvent(event);
}
},
}
</script>
<style scoped>
<template>
<div class="d-flex align-center">
<div>
<div v-for="(val, i) of enumValues" :key="val" class="item">
<input :id="`key-radio-${val}`" v-model="localState" type="radio" class="orange--text" :value="val" />
<label
class="py-1 px-3 d-inline-block my-1 label"
:for="`key-radio-${val}`"
:style="{
background: colors[i % colors.length],
}"
>{{ val }}</label
>
</div>
</div>
</div>
</template>
<style scoped>
.label {
border-radius: 25px;
}

16
packages/nc-gui-v2/components/editable-cell/FloatCell.vue

@ -1,12 +1,8 @@
<template>
<input v-model="localState" type="number" v-on="parentListeners">
</template>
<script>
export default {
name: 'FloatCell',
props: {
value: [String, Number]
value: [String, Number],
},
computed: {
localState: {
@ -15,7 +11,7 @@ export default {
},
set(val) {
this.$emit('input', +val)
}
},
},
parentListeners() {
const $listeners = {}
@ -28,14 +24,18 @@ export default {
}
return $listeners
}
},
},
mounted() {
this.$el.focus()
}
},
}
</script>
<template>
<input v-model="localState" type="number" v-on="parentListeners" />
</template>
<style scoped>
input {
width: 100%;

16
packages/nc-gui-v2/components/editable-cell/IntegerCell.vue

@ -1,12 +1,8 @@
<template>
<input v-model="localState" type="number" v-on="parentListeners">
</template>
<script>
export default {
name: 'IntegerCell',
props: {
value: [String, Number]
value: [String, Number],
},
computed: {
localState: {
@ -15,7 +11,7 @@ export default {
},
set(val) {
this.$emit('input', parseInt(val, 10))
}
},
},
parentListeners() {
const $listeners = {}
@ -28,14 +24,18 @@ export default {
}
return $listeners
}
},
},
mounted() {
this.$el.focus()
}
},
}
</script>
<template>
<input v-model="localState" type="number" v-on="parentListeners" />
</template>
<style scoped>
input {
width: 100%;

102
packages/nc-gui-v2/components/editable-cell/JsonEditableCell.vue

@ -1,45 +1,3 @@
<template>
<v-dialog :is="expand ? 'v-dialog' : 'div'" v-model="expand" max-width="800px" class="cell-container" @keydown.stop.enter>
<div class="d-flex pa-1 " :class="{backgroundColor:expand}">
<v-spacer />
<v-icon small class="mr-2" @click="expand = !expand">
{{ expand ? 'mdi-arrow-collapse' : 'mdi-arrow-expand' }}
</v-icon>
<template v-if="!isForm">
<v-btn outlined x-small class="mr-1" @click="$emit('cancel')">
<!-- Cancel -->
{{ $t('general.cancel') }}
</v-btn>
<v-btn x-small color="primary" :disabled="!isValid" @click="save">
<!-- Save -->
{{ $t('general.save') }}
</v-btn>
</template>
<v-btn v-else-if="expand" x-small @click="expand=false">
<!-- Close -->
{{ $t('general.close') }}
</v-btn>
</div>
<monaco-json-object-editor
v-if="expand"
v-model="localState"
class="text-left caption"
style="width: 300px;min-height:min(600px,80vh);min-width:100%; "
@validate="validate"
/>
<monaco-json-object-editor
v-else
v-model="localState"
class="text-left caption"
style="width: 300px;min-height:200px;min-width:100%;"
@validate="validate"
/>
<div v-show="error" class="px-2 py-1 text-left caption error--text">
{{ error }}
</div>
</v-dialog>
</template>
<script>
import MonacoJsonObjectEditor from '@/components/monaco/MonacoJsonObjectEditor'
@ -48,16 +6,15 @@ export default {
components: { MonacoJsonObjectEditor },
props: {
value: [String, Object],
isForm: Boolean
isForm: Boolean,
},
data: () => ({
localState: '',
expand: false,
isValid: true,
error: undefined
error: undefined,
}),
computed: {
parentListeners() {
const $listeners = {}
@ -69,7 +26,7 @@ export default {
}
return $listeners
}
},
},
watch: {
value(val) {
@ -83,7 +40,7 @@ export default {
if (this.isForm) {
this.$emit('input', JSON.stringify(val))
}
}
},
},
created() {
try {
@ -92,8 +49,7 @@ export default {
// ignore parse error for invalid JSON
}
},
mounted() {
},
mounted() {},
methods: {
save() {
this.expand = false
@ -102,14 +58,56 @@ export default {
validate(n, e) {
this.isValid = n
this.error = e
}
}
},
},
}
</script>
<template>
<v-dialog :is="expand ? 'v-dialog' : 'div'" v-model="expand" max-width="800px" class="cell-container" @keydown.stop.enter>
<div class="d-flex pa-1" :class="{ backgroundColor: expand }">
<v-spacer />
<v-icon small class="mr-2" @click="expand = !expand">
{{ expand ? 'mdi-arrow-collapse' : 'mdi-arrow-expand' }}
</v-icon>
<template v-if="!isForm">
<v-btn outlined x-small class="mr-1" @click="$emit('cancel')">
<!-- Cancel -->
{{ $t('general.cancel') }}
</v-btn>
<v-btn x-small color="primary" :disabled="!isValid" @click="save">
<!-- Save -->
{{ $t('general.save') }}
</v-btn>
</template>
<v-btn v-else-if="expand" x-small @click="expand = false">
<!-- Close -->
{{ $t('general.close') }}
</v-btn>
</div>
<MonacoJsonObjectEditor
v-if="expand"
v-model="localState"
class="text-left caption"
style="width: 300px; min-height: min(600px, 80vh); min-width: 100%"
@validate="validate"
/>
<MonacoJsonObjectEditor
v-else
v-model="localState"
class="text-left caption"
style="width: 300px; min-height: 200px; min-width: 100%"
@validate="validate"
/>
<div v-show="error" class="px-2 py-1 text-left caption error--text">
{{ error }}
</div>
</v-dialog>
</template>
<style scoped>
.cell-container {
width: 100%
width: 100%;
}
</style>
<!--

59
packages/nc-gui-v2/components/editable-cell/RatingCell.vue

@ -1,38 +1,10 @@
<template>
<div class="d-100 h-100" :class="{'nc-cell-hover-show': localState == 0 || !localState}">
<v-rating
v-model="localState"
:length="ratingMeta.max"
dense
x-small
:readonly="readOnly"
clearable
>
<template #item="{isFilled, click}">
<v-icon v-if="isFilled" :size="15" :color="ratingMeta.color" @click="click">
{{ fullIcon }}
</v-icon>
<v-icon
v-else
:color="ratingMeta.color"
:size="15"
class="nc-cell-hover-show"
@click="click"
>
{{ emptyIcon }}
</v-icon>
</template>
</v-rating>
</div>
</template>
<script>
export default {
name: 'RatingCell',
props: {
column: Object,
value: [String, Number],
readOnly: Boolean
readOnly: Boolean,
},
computed: {
fullIcon() {
@ -47,25 +19,36 @@ export default {
},
set(val) {
this.$emit('input', val)
}
},
},
ratingMeta() {
return {
icon: {
full: 'mdi-star',
empty: 'mdi-star-outline'
empty: 'mdi-star-outline',
},
color: '#fcb401',
max: 5,
...(this.column && this.column.meta
? this.column.meta
: {})
...(this.column && this.column.meta ? this.column.meta : {}),
}
}
}
},
},
}
</script>
<style scoped>
<template>
<div class="d-100 h-100" :class="{ 'nc-cell-hover-show': localState == 0 || !localState }">
<v-rating v-model="localState" :length="ratingMeta.max" dense x-small :readonly="readOnly" clearable>
<template #item="{ isFilled, click }">
<v-icon v-if="isFilled" :size="15" :color="ratingMeta.color" @click="click">
{{ fullIcon }}
</v-icon>
<v-icon v-else :color="ratingMeta.color" :size="15" class="nc-cell-hover-show" @click="click">
{{ emptyIcon }}
</v-icon>
</template>
</v-rating>
</div>
</template>
</style>
<style scoped></style>

50
packages/nc-gui-v2/components/editable-cell/SetListCheckboxCell.vue

@ -1,20 +1,3 @@
<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>
<script>
import colors from '@/components/project/spreadsheet/helpers/colors'
@ -23,10 +6,9 @@ export default {
props: {
value: String,
column: Object,
values: Array
},
data() {
values: Array,
},
data() {},
computed: {
colors() {
return this.$store.state.settings.darkTheme ? colors.dark : colors.light
@ -38,11 +20,11 @@ export default {
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.column.dtxp.split(',').map((v) => v.replace(/^'|'$/g, ''))
}
return this.values || []
},
@ -57,23 +39,39 @@ export default {
}
return $listeners
}
},
},
mounted() {
this.$el.focus()
const event = document.createEvent('MouseEvents')
event.initMouseEvent('mousedown', true, true, window)
this.$el.dispatchEvent(event)
}
},
}
</script>
<style scoped>
<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>
<!--
/**

97
packages/nc-gui-v2/components/editable-cell/SetListEditableCell.vue

@ -1,43 +1,3 @@
<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>
<script>
import colors from '@/mixins/colors'
@ -46,24 +6,20 @@ export default {
mixins: [colors],
props: {
value: String,
column: Object
column: Object,
},
computed: {
localState: {
get() {
return this.value && this.value
.match(/(?:[^',]|\\')+(?='?(?:,|$))/g)
.map(v => v.replace(/\\'/g, '\''))
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(','))
}
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 this.column.dtxp.match(/(?:[^']|\\')+(?='?(?:,|$))/g).map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, ''))
}
return []
},
@ -78,7 +34,7 @@ export default {
}
return $listeners
}
},
},
mounted() {
// this.$el.focus();
@ -86,10 +42,48 @@ export default {
// 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%;
@ -101,7 +95,6 @@ select {
/*Firefox */
appearance: menulist;
}
</style>
<!--
/**

34
packages/nc-gui-v2/components/editable-cell/TextAreaCell.vue

@ -1,29 +1,17 @@
<template>
<textarea
ref="textarea"
v-model="localState"
rows="4"
v-on="parentListeners"
@keydown.alt.enter.stop
@keydown.shift.enter.stop
/>
</template>
<script>
export default {
name: 'TextAreaCell',
props: {
value: String
value: String,
},
computed: {
localState: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
},
},
parentListeners() {
const $listeners = {}
@ -36,19 +24,31 @@ export default {
}
return $listeners
}
},
},
created() {
this.localState = this.value
},
mounted() {
this.$refs.textarea && this.$refs.textarea.focus()
}
},
}
</script>
<template>
<textarea
ref="textarea"
v-model="localState"
rows="4"
v-on="parentListeners"
@keydown.alt.enter.stop
@keydown.shift.enter.stop
/>
</template>
<style scoped>
input, textarea {
input,
textarea {
width: 100%;
min-height: 60px;
height: 100%;

59
packages/nc-gui-v2/components/editable-cell/TextAreaCellOld.vue

@ -1,39 +1,14 @@
<template>
<div>
<div v-if="!isForm" class="d-flex ma-1">
<v-spacer />
<v-btn v-if="!isForm" outlined x-small class="mr-1" @click="$emit('cancel')">
<!-- Cancel -->
{{ $t('general.cancel') }}
</v-btn>
<v-btn v-if="!isForm" x-small color="primary" @click="save">
<!-- Save -->
{{ $t('general.save') }}
</v-btn>
</div>
<textarea
ref="textarea"
v-model="localState"
rows="3"
v-on="parentListeners"
@input="isForm && save()"
@keydown.stop.enter
/>
</div>
</template>
<script>
export default {
name: 'TextAreaCell',
props: {
value: String,
isForm: Boolean
isForm: Boolean,
},
data: () => ({
localState: ''
localState: '',
}),
computed: {
parentListeners() {
const $listeners = {}
@ -45,7 +20,7 @@ export default {
}
return $listeners
}
},
},
watch: {
value(val) {
@ -55,7 +30,7 @@ export default {
if (this.isForm) {
this.$emit('input', val)
}
}
},
},
created() {
this.localState = this.value
@ -66,15 +41,33 @@ export default {
methods: {
save() {
this.$emit('input', this.localState)
}
}
},
},
}
</script>
<template>
<div>
<div v-if="!isForm" class="d-flex ma-1">
<v-spacer />
<v-btn v-if="!isForm" outlined x-small class="mr-1" @click="$emit('cancel')">
<!-- Cancel -->
{{ $t('general.cancel') }}
</v-btn>
<v-btn v-if="!isForm" x-small color="primary" @click="save">
<!-- Save -->
{{ $t('general.save') }}
</v-btn>
</div>
<textarea ref="textarea" v-model="localState" rows="3" v-on="parentListeners" @input="isForm && save()" @keydown.stop.enter />
</div>
</template>
<style scoped>
input, textarea {
input,
textarea {
width: 100%;
min-height:60px;
min-height: 60px;
height: calc(100% - 28px);
color: var(--v-textColor-base);
}

19
packages/nc-gui-v2/components/editable-cell/TextCell.vue

@ -1,12 +1,8 @@
<template>
<input v-model="localState" v-on="parentListeners">
</template>
<script>
export default {
name: 'TextCell',
props: {
value: [String, Object, Number, Boolean, Array]
value: [String, Object, Number, Boolean, Array],
},
computed: {
localState: {
@ -15,7 +11,7 @@ export default {
},
set(val) {
this.$emit('input', val)
}
},
},
parentListeners() {
const $listeners = {}
@ -32,16 +28,21 @@ export default {
}
return $listeners
}
},
},
mounted() {
this.$el.focus()
}
},
}
</script>
<template>
<input v-model="localState" v-on="parentListeners" />
</template>
<style scoped>
input, textarea {
input,
textarea {
width: 100%;
height: 100%;
color: var(--v-textColor-base);

39
packages/nc-gui-v2/components/editable-cell/TimePickerCell.vue

@ -1,25 +1,10 @@
<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>
<script>
import dayjs from 'dayjs'
export default {
name: 'TimePickerCell',
props: {
value: [String, Date]
value: [String, Date],
},
computed: {
isMysql() {
@ -51,8 +36,7 @@ export default {
this.$emit('input', dateTime.format('YYYY-MM-DD HH:mm:ssZ'))
}
}
}
},
},
parentListeners() {
const $listeners = {}
@ -69,16 +53,31 @@ export default {
}
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%;

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

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import useColumn from '~/composables/useColumn'
import useColumn from '~/composables/useColumn'
const { column, value } = defineProps<{ column: ColumnType; value: any }>()
provide('column', column)
@ -38,11 +38,7 @@ const {
<!-- <CellDuration v-else-if="isDuration" /> -->
<!-- <CellRating v-else-if="isRating" /> -->
<!-- <CellCurrency v-else-if="isCurrency" /> -->
<span
v-else
:title="title"
>{{ value }}</span>
<span v-else :title="title">{{ value }}</span>
</template>
<style scoped>
</style>
<style scoped></style>

82
packages/nc-gui-v2/components/smartsheet/EditableCell.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import useColumn from '~/composables/useColumn'
import useColumn from '~/composables/useColumn'
const { column, value } = defineProps<{ column: ColumnType; value: any }>()
provide('column', column)
@ -25,13 +25,7 @@ const {
</script>
<template>
<div
class="nc-cell"
@keydown.stop.left
@keydown.stop.right
@keydown.stop.up
@keydown.stop.down
>
<div class="nc-cell" @keydown.stop.left @keydown.stop.right @keydown.stop.up @keydown.stop.down>
<EditableAttachmentCell
v-if="isAttachment"
v-model="localState"
@ -69,45 +63,17 @@ const {
v-on="parentListeners"
/>
<BooleanCell
v-else-if="isBoolean"
v-model="localState"
:column="column"
:is-form="isForm"
v-on="parentListeners"
/>
<BooleanCell v-else-if="isBoolean" v-model="localState" :column="column" :is-form="isForm" v-on="parentListeners" />
<IntegerCell
v-else-if="isInt"
v-model="localState"
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"
/>
<FloatCell v-else-if="isFloat" v-model="localState" v-on="parentListeners" />
<DatePickerCell
v-else-if="isDate"
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')"
/>
<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"
/>
<DateTimePickerCell v-else-if="isDateTime" v-model="localState" ignore-focus v-on="parentListeners" />
<EnumCell
v-else-if="isEnum && ((!isForm && !active) || isLocked || (isPublic && !isForm))"
@ -115,21 +81,9 @@ const {
:column="column"
v-on="parentListeners"
/>
<EnumListCell
v-else-if="isEnum"
v-model="localState"
:is-form="isForm"
: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')"
/>
<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)"
@ -137,23 +91,13 @@ const {
:column="column"
v-on="parentListeners"
/>
<SetListCell
v-else-if="isSet"
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" />
<TextCell v-else-if="isString" v-model="localState" v-on="parentListeners" />
<TextAreaCell
v-else-if="isTextArea"
v-model="localState"
:is-form="isForm"
v-on="parentListeners"
/>
<TextAreaCell v-else-if="isTextArea" v-model="localState" :is-form="isForm" v-on="parentListeners" />
<TextCell v-else v-model="localState" v-on="$listeners" />
<span v-if="hint" class="nc-hint">{{ hint }}</span>
@ -170,7 +114,7 @@ div {
}
.nc-hint {
font-size: .61rem;
font-size: 0.61rem;
color: grey;
}

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

@ -1,8 +1,9 @@
<script lang="ts" setup>
import { inject, ComputedRef, onMounted } from "vue";
import type { ComputedRef } from 'vue'
import { inject, onMounted } from 'vue'
import { isVirtualCol } from 'nocodb-sdk'
import type { TableType } from 'nocodb-sdk'
import useViewData from '~/composables/useViewData'
import useViewData from '~/composables/useViewData'
const meta = inject<ComputedRef<TableType>>('meta')
@ -12,34 +13,24 @@ onMounted(() => loadData({}))
</script>
<template>
<table
class="xc-row-table nc-grid backgroundColorDefault"
>
<table class="xc-row-table nc-grid backgroundColorDefault">
<thead>
<tr>
<th>#</th>
<th v-for="(col) in meta.columns" :key="col.title">
<th v-for="col in meta.columns" :key="col.title">
{{ col.title }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="({ row }, rowIndex) in data"
:key="rowIndex"
class="nc-grid-row"
>
<td
key="row-index"
style="width: 65px"
class="caption nc-grid-cell"
>
<tr v-for="({ row }, rowIndex) in data" :key="rowIndex" class="nc-grid-row">
<td key="row-index" style="width: 65px" class="caption nc-grid-cell">
<div class="d-flex align-center">
{{ rowIndex + 1 }}
</div>
</td>
<td
v-for="(columnObj) in meta.columns"
v-for="columnObj in meta.columns"
:key="rowIndex + columnObj.title"
class="cell pointer nc-grid-cell"
:class="{
@ -128,18 +119,18 @@ onMounted(() => loadData({}))
:column="columnObj"
:value="row[columnObj.title]"
/>
<!-- :selected="selected.col === col && selected.row === row" -->
<!-- :is-locked="isLocked" -->
<!-- :column="columnObj" -->
<!-- :meta="meta" -->
<!-- :db-alias="nodes.dbAlias" -->
<!-- :value="rowObj[columnObj.title]" -->
<!-- :sql-ui="sqlUi" -->
<!-- @enableedit=" -->
<!-- makeSelected(col, row); -->
<!-- makeEditable(col, row, columnObj.ai, rowMeta); -->
<!-- " -->
<!-- /> -->
<!-- :selected="selected.col === col && selected.row === row" -->
<!-- :is-locked="isLocked" -->
<!-- :column="columnObj" -->
<!-- :meta="meta" -->
<!-- :db-alias="nodes.dbAlias" -->
<!-- :value="rowObj[columnObj.title]" -->
<!-- :sql-ui="sqlUi" -->
<!-- @enableedit=" -->
<!-- makeSelected(col, row); -->
<!-- makeEditable(col, row, columnObj.ai, rowMeta); -->
<!-- " -->
<!-- /> -->
</td>
</tr>
</tbody>
@ -164,10 +155,10 @@ th {
border-top: 1px solid #7f828b33 !important;
border-collapse: collapse;
font-size:.8rem;
font-size: 0.8rem;
}
td{
td {
text-overflow: ellipsis;
white-space: nowrap;
}

10
packages/nc-gui-v2/components/tabs/Smartsheet.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, onMounted, provide, watch } from 'vue'
import useMetas from '~/composables/useMetas'
import useMetas from '~/composables/useMetas'
const { tabMeta } = defineProps({
tabMeta: Object,
@ -19,7 +19,6 @@ onMounted(async () => {
provide('meta', meta)
provide('tabMeta', tabMeta)
watch(
() => tabMeta && tabMeta?.id,
async (newVal, oldVal) => {
@ -30,12 +29,7 @@ watch(
<template>
<div class="overflow-auto">
<v-toolbar
height="32"
dense
class="nc-table-toolbar elevation-0 xc-toolbar xc-border-bottom mx-1"
style="z-index: 7"
/>
<v-toolbar height="32" dense class="nc-table-toolbar elevation-0 xc-toolbar xc-border-bottom mx-1" style="z-index: 7" />
<template v-if="meta && tabMeta">
<SmartsheetGrid />
</template>

66
packages/nc-gui-v2/composables/useColumn.ts

@ -1,30 +1,33 @@
import { SqlUiFactory, UITypes, isVirtualCol, ColumnType } from "nocodb-sdk";
import useProject from "~/composables/useProject";
import type { ColumnType } from 'nocodb-sdk'
import { SqlUiFactory, UITypes, isVirtualCol } from 'nocodb-sdk'
import useProject from '~/composables/useProject'
export default (column: ColumnType) => {
const { project } = useProject();
const { project } = useProject()
const uiDatatype: UITypes = (column && column.uidt) as UITypes;
const abstractType = isVirtualCol(column) ? null : SqlUiFactory.create(project.value?.bases?.[0]?.config || { client: "mysql2" }).getAbstractType(column);
const uiDatatype: UITypes = (column && column.uidt) as UITypes
const abstractType = isVirtualCol(column)
? null
: SqlUiFactory.create(project.value?.bases?.[0]?.config || { client: 'mysql2' }).getAbstractType(column)
const dataTypeLow = column && column.dt && column.dt.toLowerCase();
const isBoolean = abstractType === "boolean";
const isString = abstractType === "string";
const isTextArea = uiDatatype === UITypes.LongText;
const isInt = abstractType === "integer";
const isFloat = abstractType === "float";
const isDate = abstractType === "date" || uiDatatype === "Date";
const isTime = abstractType === "time" || uiDatatype === "Time";
const isDateTime = abstractType === "datetime" || uiDatatype === "DateTime";
const isJSON = uiDatatype === "JSON";
const isEnum = uiDatatype === "SingleSelect";
const isSet = uiDatatype === "MultiSelect";
const isURL = uiDatatype === "URL";
const isEmail = uiDatatype === UITypes.Email;
const isAttachment = uiDatatype === "Attachment";
const isRating = uiDatatype === UITypes.Rating;
const isCurrency = uiDatatype === "Currency";
const isDuration = uiDatatype === UITypes.Duration;
const dataTypeLow = column && column.dt && column.dt.toLowerCase()
const isBoolean = abstractType === 'boolean'
const isString = abstractType === 'string'
const isTextArea = uiDatatype === UITypes.LongText
const isInt = abstractType === 'integer'
const isFloat = abstractType === 'float'
const isDate = abstractType === 'date' || uiDatatype === 'Date'
const isTime = abstractType === 'time' || uiDatatype === 'Time'
const isDateTime = abstractType === 'datetime' || uiDatatype === 'DateTime'
const isJSON = uiDatatype === 'JSON'
const isEnum = uiDatatype === 'SingleSelect'
const isSet = uiDatatype === 'MultiSelect'
const isURL = uiDatatype === 'URL'
const isEmail = uiDatatype === UITypes.Email
const isAttachment = uiDatatype === 'Attachment'
const isRating = uiDatatype === UITypes.Rating
const isCurrency = uiDatatype === 'Currency'
const isDuration = uiDatatype === UITypes.Duration
const isAutoSaved = [
UITypes.SingleLineText,
UITypes.LongText,
@ -37,14 +40,9 @@ export default (column: ColumnType) => {
UITypes.Count,
UITypes.AutoNumber,
UITypes.SpecificDBType,
UITypes.Geometry
].includes(uiDatatype);
const isManualSaved = [
UITypes.Currency,
UITypes.Year,
UITypes.Time,
UITypes.Duration
].includes(uiDatatype);
UITypes.Geometry,
].includes(uiDatatype)
const isManualSaved = [UITypes.Currency, UITypes.Year, UITypes.Time, UITypes.Duration].includes(uiDatatype)
return {
abstractType,
@ -67,6 +65,6 @@ export default (column: ColumnType) => {
isCurrency,
isDuration,
isAutoSaved,
isManualSaved
};
};
isManualSaved,
}
}

2
packages/nc-gui-v2/composables/useMetas.ts

@ -1,6 +1,6 @@
import type { TableType } from 'nocodb-sdk'
import { useNuxtApp, useState } from '#app'
import useProject from "~/composables/useProject";
import useProject from '~/composables/useProject'
export default () => {
const { $api } = useNuxtApp()

24
packages/nc-gui-v2/composables/useProject.ts

@ -1,23 +1,23 @@
import type { ProjectType, TableType } from "nocodb-sdk";
import { useNuxtApp } from "#app";
import type { ProjectType, TableType } from 'nocodb-sdk'
import { useNuxtApp } from '#app'
export default () => {
const { $api } = useNuxtApp();
const { $api } = useNuxtApp()
const project = useState<ProjectType>("project");
const tables = useState<Array<TableType>>("tables");
const project = useState<ProjectType>('project')
const tables = useState<Array<TableType>>('tables')
const loadTables = async () => {
if (project.value.id) {
const tablesResponse = await $api.dbTable.list(project.value.id);
const tablesResponse = await $api.dbTable.list(project.value.id)
if (tablesResponse.list) tables.value = tablesResponse.list;
if (tablesResponse.list) tables.value = tablesResponse.list
}
};
}
const loadProject = async (projectId: string) => {
project.value = await $api.project.read(projectId);
};
project.value = await $api.project.read(projectId)
}
return { project, tables, loadProject, loadTables };
};
return { project, tables, loadProject, loadTables }
}

53
packages/nc-gui-v2/composables/useViewData.ts

@ -1,34 +1,29 @@
import { Api } from "nocodb-sdk";
import type { ComputedRef, Ref } from "vue";
import type { PaginatedType, TableType } from "nocodb-sdk";
import { useNuxtApp } from "#app";
import useProject from "~/composables/useProject";
import type { Api, PaginatedType, TableType } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue'
import { useNuxtApp } from '#app'
import useProject from '~/composables/useProject'
const formatData = (list: Array<Record<string, any>>) => list.map(row => ({
row: { ...row },
oldRow: { ...row },
rowMeta: {}
}));
const formatData = (list: Array<Record<string, any>>) =>
list.map((row) => ({
row: { ...row },
oldRow: { ...row },
rowMeta: {},
}))
export default (meta: Ref<TableType> | ComputedRef<TableType> | undefined) => {
const data = ref<Array<Record<string, any>>>();
const formattedData = ref<Array<{ row: Record<string, any>; oldRow: Record<string, any> }>>();
const paginationData = ref<PaginatedType>();
export default (meta: Ref<TableType> | ComputedRef<TableType> | undefined) => {
const data = ref<Array<Record<string, any>>>()
const formattedData = ref<Array<{ row: Record<string, any>; oldRow: Record<string, any> }>>()
const paginationData = ref<PaginatedType>()
const { project } = useProject();
const { $api } = useNuxtApp();
const { project } = useProject()
const { $api } = useNuxtApp()
const loadData = async (params: Parameters<Api<any>["dbTableRow"]["list"]>[3] = {}) => {
if(!project?.value?.id || !meta?.value?.id) return
const response = await $api.dbTableRow.list(
"noco",
project.value.id,
meta.value.id,
params
);
data.value = response.list;
formattedData.value = formatData(response.list);
};
const loadData = async (params: Parameters<Api<any>['dbTableRow']['list']>[3] = {}) => {
if (!project?.value?.id || !meta?.value?.id) return
const response = await $api.dbTableRow.list('noco', project.value.id, meta.value.id, params)
data.value = response.list
formattedData.value = formatData(response.list)
}
return { data, loadData, paginationData, formattedData };
};
return { data, loadData, paginationData, formattedData }
}

37
packages/nc-gui-v2/pages/nc/[projectId].vue

@ -1,28 +1,27 @@
<script setup lang="ts">
import { watch } from "vue";
import useProject from "~/composables/useProject";
import useTabs from "~/composables/useTabs";
import { watch } from 'vue'
import useProject from '~/composables/useProject'
import useTabs from '~/composables/useTabs'
const route = useRoute();
const { loadProject, loadTables } = useProject();
const { clearTabs } = useTabs();
const route = useRoute()
const { loadProject, loadTables } = useProject()
const { clearTabs } = useTabs()
onMounted(async () => {
await loadProject(route.params.projectId as string);
await loadTables();
});
await loadProject(route.params.projectId as string)
await loadTables()
})
watch(
() => route.params.projectId,
async (newVal, oldVal) => {
if (newVal && newVal !== oldVal) {
clearTabs();
await loadProject(newVal as string);
await loadTables();
clearTabs()
await loadProject(newVal as string)
await loadTables()
}
}
);
},
)
</script>
<template>
@ -33,10 +32,10 @@ watch(
</v-navigation-drawer>
</template>
<v-container>
<DashboardTabView />
</v-container>
</NuxtLayout>
<v-container>
<DashboardTabView />
</v-container>
</NuxtLayout>
</template>
<style scoped lang="scss">

100
packages/nc-gui-v2/pages/projects/create-external.vue

@ -1,30 +1,33 @@
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useToast } from "vue-toastification";
import { useNuxtApp, useRouter } from "#app";
import { extractSdkResponseErrorMsg } from "~/utils/errorUtils";
import { clientTypes, getDefaultConnectionConfig, getTestDatabaseName } from "~/utils/projectCreateUtils";
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useToast } from 'vue-toastification'
import { useNuxtApp, useRouter } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { clientTypes, getDefaultConnectionConfig, getTestDatabaseName } from '~/utils/projectCreateUtils'
const name = ref("");
const loading = ref(false);
const valid = ref(false);
const testSuccess = ref(false);
const projectDatasource = ref(getDefaultConnectionConfig("mysql2"));
const name = ref('')
const loading = ref(false)
const valid = ref(false)
const testSuccess = ref(false)
const projectDatasource = ref(getDefaultConnectionConfig('mysql2'))
const inflection = reactive({
tableName: "camelize",
columnName: "camelize"
});
tableName: 'camelize',
columnName: 'camelize',
})
const $router = useRouter();
const { $api, $e } = useNuxtApp();
const toast = useToast();
const { t: $t } = useI18n();
const $router = useRouter()
const { $api, $e } = useNuxtApp()
const toast = useToast()
const { t: $t } = useI18n()
const titleValidationRule = [(v: string) => !!v || "Title is required", (v: string) => v.length <= 50 || "Project name exceeds 50 characters"];
const titleValidationRule = [
(v: string) => !!v || 'Title is required',
(v: string) => v.length <= 50 || 'Project name exceeds 50 characters',
]
const createProject = async () => {
loading.value = true;
loading.value = true
try {
const result = await $api.project.create({
title: name.value,
@ -33,50 +36,50 @@ const createProject = async () => {
type: projectDatasource.value.client,
config: projectDatasource.value,
inflection_column: inflection.columnName,
inflection_table: inflection.tableName
}
inflection_table: inflection.tableName,
},
],
external: true
});
external: true,
})
await $router.push(`/nc/${result.id}`);
await $router.push(`/nc/${result.id}`)
} catch (e: any) {
// todo: toast
toast.error(await extractSdkResponseErrorMsg(e));
toast.error(await extractSdkResponseErrorMsg(e))
}
loading.value = false;
};
loading.value = false
}
const testConnection = async () => {
$e("a:project:create:extdb:test-connection",[]);
$e('a:project:create:extdb:test-connection', [])
try {
// this.handleSSL(projectDatasource)
if (projectDatasource.value.client === "sqlite3") {
testSuccess.value = true;
if (projectDatasource.value.client === 'sqlite3') {
testSuccess.value = true
} else {
const testConnectionConfig = {
...projectDatasource,
connection: {
...projectDatasource.value.connection,
database: getTestDatabaseName(projectDatasource.value)
}
};
database: getTestDatabaseName(projectDatasource.value),
},
}
const result = await $api.utils.testConnection(testConnectionConfig);
const result = await $api.utils.testConnection(testConnectionConfig)
if (result.code === 0) {
testSuccess.value = true;
testSuccess.value = true
} else {
testSuccess.value = false;
toast.error($t("msg.error.dbConnectionFailed") + result.message);
testSuccess.value = false
toast.error($t('msg.error.dbConnectionFailed') + result.message)
}
}
} catch (e:any) {
testSuccess.value = false;
toast.error(await extractSdkResponseErrorMsg(e));
} catch (e: any) {
testSuccess.value = false
toast.error(await extractSdkResponseErrorMsg(e))
}
};
}
</script>
<template>
@ -93,8 +96,7 @@ const testConnection = async () => {
<div class="mx-auto" style="width: 350px">
<!-- label="Enter Project Name" -->
<!-- rule text: Required -->
<v-text-field v-model="name" :rules="titleValidationRule" class="nc-metadb-project-name"
label="Project name" />
<v-text-field v-model="name" :rules="titleValidationRule" class="nc-metadb-project-name" label="Project name" />
<!-- :rules="titleValidationRule" -->
</div>
@ -115,8 +117,7 @@ const testConnection = async () => {
<v-text-field v-model="projectDatasource.connection.host" density="compact" label="Host" />
</v-col>
<v-col cols="6">
<v-text-field v-model="projectDatasource.connection.port" density="compact" label="Port"
type="number" />
<v-text-field v-model="projectDatasource.connection.port" density="compact" label="Port" type="number" />
</v-col>
<v-col cols="6">
<v-text-field v-model="projectDatasource.connection.user" density="compact" label="Username" />
@ -130,8 +131,7 @@ const testConnection = async () => {
/>
</v-col>
<v-col cols="6">
<v-text-field v-model="projectDatasource.connection.database" density="compact"
label="Database name" />
<v-text-field v-model="projectDatasource.connection.database" density="compact" label="Database name" />
</v-col>
<!-- <v-col cols="6">
@ -156,12 +156,12 @@ const testConnection = async () => {
<v-btn :disabled="!testSuccess" class="" large :loading="loading" color="primary" @click="createProject">
<v-icon class="mr-1 mt-n1"> mdi-rocket-launch-outline</v-icon>
<!-- Create -->
<span class="mr-1">{{ $t("general.create") }} </span>
<span class="mr-1">{{ $t('general.create') }} </span>
</v-btn>
<v-btn size="sm" class="text-sm text-capitalize">
<!-- Test Database Connection -->
{{ $t("activity.testDbConn") }}
{{ $t('activity.testDbConn') }}
</v-btn>
</div>
</v-container>

11
packages/nc-gui-v2/pages/projects/create.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { useNuxtApp, useRouter } from '#app'
import { extractSdkResponseErrorMsg } from "~/utils/errorUtils";
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
const name = ref('')
const loading = ref(false)
@ -10,7 +10,10 @@ const valid = ref(false)
const { $api, $toast } = useNuxtApp()
const $router = useRouter()
const titleValidationRule = [(v:string) => !!v || 'Title is required', (v:string) => v.length <= 50 || 'Project name exceeds 50 characters']
const titleValidationRule = [
(v: string) => !!v || 'Title is required',
(v: string) => v.length <= 50 || 'Project name exceeds 50 characters',
]
const createProject = async () => {
loading.value = true
@ -20,7 +23,7 @@ const createProject = async () => {
})
await $router.push(`/nc/${result.id}`)
} catch (e:any) {
} catch (e: any) {
// todo: toast
$toast.error(await extractSdkResponseErrorMsg(e)).goAway(3000)
}
@ -59,7 +62,7 @@ const createProject = async () => {
</template>
<style scoped>
/deep/ label {
:deep(label) {
font-size: 0.75rem;
}

Loading…
Cancel
Save