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> <template>
<NuxtPage /> <NuxtPage />
</template> </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> <template>
<div <div
class="d-flex align-center img-container d-100 h-100" class="d-flex align-center img-container d-100 h-100"
@ -11,9 +35,7 @@
> >
<div v-if="dragOver" class="drop-overlay"> <div v-if="dragOver" class="drop-overlay">
<div> <div>
<v-icon small> <v-icon small> mdi-cloud-upload-outline </v-icon>
mdi-cloud-upload-outline
</v-icon>
<span class="caption font-weight-bold">Drop here</span> <span class="caption font-weight-bold">Drop here</span>
</div> </div>
</div> </div>
@ -21,14 +43,12 @@
<div v-for="item in localState" :key="item.title" class="thumbnail d-flex align-center justify-center"> <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-lazy class="d-flex align-center justify-center">
<v-tooltip bottom> <v-tooltip bottom>
<template #activator="{on}"> <template #activator="{ on }">
<img v-if="isImage(item.title)" alt="#" :src="item.url" v-on="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"> <v-icon v-else-if="item.icon" size="33" v-on="on">
{{ item.icon }} {{ item.icon }}
</v-icon> </v-icon>
<v-icon v-else size="33" v-on="on"> <v-icon v-else size="33" v-on="on"> mdi-file </v-icon>
mdi-file
</v-icon>
</template> </template>
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
</v-tooltip> </v-tooltip>
@ -38,30 +58,6 @@
</div> </div>
</template> </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> <style scoped>
.img-container { .img-container {
margin: 0 -2px; margin: 0 -2px;
@ -95,7 +91,6 @@ export default {
align-items: center; align-items: center;
pointer-events: none; pointer-events: none;
} }
</style> </style>
<!-- <!--
/** /**

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

@ -6,6 +6,6 @@ const value = inject<boolean | number>('value')
<template> <template>
<div class="d-flex align-center"> <div class="d-flex align-center">
<input v-model="value" type="checkbox" :disabled="true"> <input v-model="value" type="checkbox" :disabled="true" />
</div> </div>
</template> </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> <script>
export default { export default {
name: 'CurrencyCell', name: 'CurrencyCell',
props: { props: {
column: Object, column: Object,
value: [String, Number] value: [String, Number],
}, },
computed: { computed: {
currency() { currency() {
try { try {
return isNaN(this.value) return isNaN(this.value)
? this.value ? this.value
: new Intl.NumberFormat(this.currencyMeta.currency_locale || 'en-US', : new Intl.NumberFormat(this.currencyMeta.currency_locale || 'en-US', {
{ style: 'currency', currency: this.currencyMeta.currency_code || 'USD' }) style: 'currency',
.format(this.value) currency: this.currencyMeta.currency_code || 'USD',
}).format(this.value)
} catch (e) { } catch (e) {
return this.value return this.value
} }
@ -26,15 +22,16 @@ export default {
return { return {
currency_locale: 'en-US', currency_locale: 'en-US',
currency_code: 'USD', currency_code: 'USD',
...(this.column && this.column.meta ...(this.column && this.column.meta ? this.column.meta : {}),
? this.column.meta
: {})
} }
} },
} },
} }
</script> </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> <script>
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -11,11 +7,13 @@ export default {
computed: { computed: {
date() { date() {
return (/^\d+$/.test(this.value) ? dayjs(+this.value) : dayjs(this.value)).format('YYYY-MM-DD') return (/^\d+$/.test(this.value) ? dayjs(+this.value) : dayjs(this.value)).format('YYYY-MM-DD')
} },
} },
} }
</script> </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> <script>
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -10,12 +6,16 @@ export default {
props: ['value'], props: ['value'],
computed: { computed: {
dateTime() { 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> </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> <script>
import { durationOptions, convertMS2Duration } from '~/helpers/durationHelper' import { convertMS2Duration, durationOptions } from '~/helpers/durationHelper'
export default { export default {
name: 'DurationCell', name: 'DurationCell',
props: { props: {
column: Object, column: Object,
value: [String, Number] value: [String, Number],
}, },
data: () => ({ data: () => ({
showWarningMessage: false, showWarningMessage: false,
localValue: null localValue: null,
}), }),
computed: { computed: {
durationPlaceholder() { durationPlaceholder() {
return durationOptions[this.column?.meta?.duration || 0].title return durationOptions[this.column?.meta?.duration || 0].title
} },
}, },
watch: { watch: {
'column.meta.duration'(newValue, oldValue) { 'column.meta.duration': function (newValue, oldValue) {
if (oldValue !== newValue) { if (oldValue !== newValue) {
this.localValue = convertMS2Duration(this.value, newValue) this.localValue = convertMS2Duration(this.value, newValue)
} }
}, },
value(val, oldVal) { 'value': function (val, oldVal) {
this.localValue = convertMS2Duration(val !== oldVal && (!val && val !== 0) ? oldVal : val, this.column?.meta?.duration || 0) this.localValue = convertMS2Duration(val !== oldVal && !val && val !== 0 ? oldVal : val, this.column?.meta?.duration || 0)
} },
}, },
created() { created() {
this.localValue = convertMS2Duration(this.value, this.column?.meta?.duration || 0) this.localValue = convertMS2Duration(this.value, this.column?.meta?.duration || 0)
} },
} }
</script> </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> <script>
import { isEmail } from '~/helpers' import { isEmail } from '~/helpers'
@ -12,11 +7,14 @@ export default {
computed: { computed: {
isEmail() { isEmail() {
return isEmail(this.value || '') return isEmail(this.value || '')
} },
} },
} }
</script> </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"> <script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { inject } from 'vue' import { inject } from 'vue'
import { enumColor } from "~/utils/colorsUtils"; import { enumColor } from '~/utils/colorsUtils'
const colors = enumColor.light const colors = enumColor.light
@ -18,7 +18,8 @@ const column = inject<ColumnType>('column')
background: colors[v], background: colors[v],
}" }"
class="set-item ma-1 py-1 px-3" class="set-item ma-1 py-1 px-3"
>{{ v }}</span> >{{ v }}</span
>
</div> </div>
</template> </template>

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

@ -1,14 +1,12 @@
<template>
<pre class="text-left caption">{{ value }}</pre>
</template>
<script> <script>
export default { export default {
name: 'JsonCell', name: 'JsonCell',
props: ['value'] props: ['value'],
} }
</script> </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: { computed: {
setValues() { setValues() {
if (this.column && this.column.dtxp) 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 [] return []
}, },
selectedValues() { 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> <script>
export default { export default {
name: 'TimeCell', name: 'TimeCell',
@ -9,11 +5,13 @@ export default {
computed: { computed: {
time() { time() {
return typeof this.value === 'string' ? this.value.replace(/(\d)T(?=\d)/, '$1 ') : this.value return typeof this.value === 'string' ? this.value.replace(/(\d)T(?=\d)/, '$1 ') : this.value
} },
} },
} }
</script> </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> <script>
import { isValidURL } from '~/helpers' import { isValidURL } from '~/helpers'
export default { export default {
@ -13,11 +7,14 @@ export default {
computed: { computed: {
isValid() { isValid() {
return this.value && isValidURL(this.value) return this.value && isValidURL(this.value)
} },
} },
} }
</script> </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"> <script setup lang="ts">
import useTabs from '~/composables/useTabs' import useTabs from '~/composables/useTabs'
const { tabs, activeTab } = useTabs() const { tabs, activeTab } = useTabs()
</script> </script>

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

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import useProject from '~/composables/useProject' import useProject from '~/composables/useProject'
import useTabs from '~/composables/useTabs' import useTabs from '~/composables/useTabs'
const { tables } = useProject() const { tables } = useProject()
const { addTab } = useTabs() 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> <script>
export default { export default {
name: 'BooleanCell', name: 'BooleanCell',
@ -13,10 +5,9 @@ export default {
column: Object, column: Object,
value: [String, Number, Boolean], value: [String, Number, Boolean],
isForm: Boolean, isForm: Boolean,
readOnly: Boolean readOnly: Boolean,
}, },
computed: { computed: {
checkedIcon() { checkedIcon() {
return (this.checkboxMeta && this.checkboxMeta.icon && this.checkboxMeta.icon.checked) || 'mdi-check-bold' return (this.checkboxMeta && this.checkboxMeta.icon && this.checkboxMeta.icon.checked) || 'mdi-check-bold'
}, },
@ -29,7 +20,7 @@ export default {
}, },
set(val) { set(val) {
this.$emit('input', val) this.$emit('input', val)
} },
}, },
parentListeners() { parentListeners() {
const $listeners = {} const $listeners = {}
@ -39,26 +30,30 @@ export default {
return { return {
icon: { icon: {
checked: 'mdi-check-circle-outline', checked: 'mdi-check-circle-outline',
unchecked: 'mdi-checkbox-blank-circle-outline' unchecked: 'mdi-checkbox-blank-circle-outline',
}, },
color: 'primary', color: 'primary',
...(this.column && this.column.meta ...(this.column && this.column.meta ? this.column.meta : {}),
? this.column.meta
: {})
} }
} },
}, },
methods: { methods: {
toggle() { toggle() {
this.localState = !this.localState this.localState = !this.localState
} },
} },
} }
</script> </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 * @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> <script>
import dayjs from 'dayjs' import dayjs from 'dayjs'
export default { export default {
name: 'DatePickerCell', name: 'DatePickerCell',
props: { props: {
value: [String, Date] value: [String, Date],
}, },
computed: { computed: {
localState: { localState: {
get() { 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') 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()) { if (dayjs(val).isValid()) {
this.$emit('input', val && dayjs(val).format('YYYY-MM-DD')) this.$emit('input', val && dayjs(val).format('YYYY-MM-DD'))
} }
} },
}, },
date() { date() {
if (!this.value || this.localState) { if (!this.value || this.localState) {
@ -50,16 +38,25 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
if (this.$el && this.$el.$el) { if (this.$el && this.$el.$el) {
this.$el.$el.focus() this.$el.$el.focus()
} }
} },
} }
</script> </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> <style scoped>
.value { .value {
width: 100%; 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> <script>
import dayjs from 'dayjs' import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc' import utc from 'dayjs/plugin/utc'
@ -35,10 +7,11 @@ dayjs.extend(utc)
export default { export default {
name: 'DateTimePickerCell', name: 'DateTimePickerCell',
props: { props: {
value: [String, Date, Number], ignoreFocus: Boolean value: [String, Date, Number],
ignoreFocus: Boolean,
}, },
data: () => ({ data: () => ({
showMessage: false showMessage: false,
}), }),
computed: { computed: {
isMysql() { isMysql() {
@ -49,7 +22,7 @@ export default {
if (!this.value) { if (!this.value) {
return 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()) { if (d.isValid()) {
this.showMessage = false this.showMessage = false
return d.format('YYYY-MM-DD HH:mm') return d.format('YYYY-MM-DD HH:mm')
@ -63,7 +36,7 @@ export default {
} else { } else {
this.$emit('input', value && dayjs(value).format('YYYY-MM-DD HH:mm:ssZ')) this.$emit('input', value && dayjs(value).format('YYYY-MM-DD HH:mm:ssZ'))
} }
} },
}, },
parentListeners() { parentListeners() {
const $listeners = {} const $listeners = {}
@ -76,7 +49,7 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
// listen dialog click:outside event and save on close // listen dialog click:outside event and save on close
@ -89,12 +62,40 @@ export default {
if (!this.ignoreFocus) { if (!this.ignoreFocus) {
this.$refs.picker.display = true this.$refs.picker.display = true
} }
} },
} }
</script> </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> <style scoped>
/deep/ .v-input, /deep/ .v-text-field { :deep(.v-input),
:deep(.v-text-field) {
margin-top: 0 !important; margin-top: 0 !important;
padding-top: 0 !important; padding-top: 0 !important;
font-size: inherit !important; font-size: inherit !important;
@ -103,7 +104,7 @@ export default {
.edit-warning { .edit-warning {
padding: 10px; padding: 10px;
text-align: left; text-align: left;
color: #E65100; color: #e65100;
} }
</style> </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> <script>
import { durationOptions, convertMS2Duration, convertDurationToSeconds } from '~/helpers/durationHelper' import { convertDurationToSeconds, convertMS2Duration, durationOptions } from '~/helpers/durationHelper'
export default { export default {
name: 'DurationCell', name: 'DurationCell',
props: { props: {
column: Object, column: Object,
value: [Number, String], value: [Number, String],
readOnly: Boolean readOnly: Boolean,
}, },
data: () => ({ data: () => ({
// flag to determine to show warning message or not // flag to determine to show warning message or not
@ -32,7 +14,7 @@ export default {
// duration in milliseconds // duration in milliseconds
durationInMS: null, durationInMS: null,
// check if the cell is edited or not // check if the cell is edited or not
isEdited: false isEdited: false,
}), }),
computed: { computed: {
localState: { localState: {
@ -45,7 +27,7 @@ export default {
if (res._isValid) { if (res._isValid) {
this.durationInMS = res._sec this.durationInMS = res._sec
} }
} },
}, },
durationPlaceholder() { durationPlaceholder() {
return durationOptions[this.durationType].title return durationOptions[this.durationType].title
@ -64,7 +46,7 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
window.addEventListener('keypress', (_) => { window.addEventListener('keypress', (_) => {
@ -76,7 +58,7 @@ export default {
methods: { methods: {
checkDurationFormat(evt) { checkDurationFormat(evt) {
evt = evt || window.event 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 // ref: http://www.columbia.edu/kermit/ascii.html
const PRINTABLE_CTL_RANGE = charCode > 31 const PRINTABLE_CTL_RANGE = charCode > 31
const NON_DIGIT = charCode < 48 || charCode > 57 const NON_DIGIT = charCode < 48 || charCode > 57
@ -96,13 +78,30 @@ export default {
this.$emit('input', this.durationInMS) this.$emit('input', this.durationInMS)
} }
this.isEdited = false this.isEdited = false
} },
} },
} }
</script> </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 { .duration-cell-wrapper {
padding: 10px; padding: 10px;
} }
@ -110,7 +109,7 @@ export default {
.duration-warning { .duration-warning {
text-align: left; text-align: left;
margin-top: 10px; margin-top: 10px;
color: #E65100; color: #e65100;
} }
</style> </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> <template>
<div <div
class="main h-100" class="main h-100"
@ -10,9 +185,7 @@
> >
<div v-show="(isForm || _isUIAllowed('tableAttachment')) && dragOver" class="drop-overlay"> <div v-show="(isForm || _isUIAllowed('tableAttachment')) && dragOver" class="drop-overlay">
<div> <div>
<v-icon small> <v-icon small> mdi-cloud-upload-outline </v-icon>
mdi-cloud-upload-outline
</v-icon>
<span class="caption font-weight-bold">Drop here</span> <span class="caption font-weight-bold">Drop here</span>
</div> </div>
</div> </div>
@ -20,12 +193,12 @@
<div class="d-flex align-center img-container"> <div class="d-flex align-center img-container">
<div class="d-flex no-overflow"> <div class="d-flex no-overflow">
<div <div
v-for="(item,i) in (isPublicForm ? localFilesState : localState)" v-for="(item, i) in isPublicForm ? localFilesState : localState"
:key="item.url || item.title" :key="item.url || item.title"
class="thumbnail align-center justify-center d-flex" class="thumbnail align-center justify-center d-flex"
> >
<v-tooltip bottom> <v-tooltip bottom>
<template #activator="{on}"> <template #activator="{ on }">
<v-img <v-img
v-if="isImage(item.title, item.mimetype)" v-if="isImage(item.title, item.mimetype)"
lazy-src="https://via.placeholder.com/60.png?text=Loading..." lazy-src="https://via.placeholder.com/60.png?text=Loading..."
@ -37,24 +210,13 @@
@click="selectImage(item.url || item.data, i)" @click="selectImage(item.url || item.data, i)"
> >
<template #placeholder> <template #placeholder>
<v-skeleton-loader <v-skeleton-loader type="image" :height="active ? 33 : 22" :width="active ? 33 : 22" />
type="image"
:height="active ? 33 : 22"
:width="active ? 33 : 22"
/>
</template> </template>
</v-img> </v-img>
<v-icon <v-icon v-else-if="item.icon" :size="active ? 33 : 22" v-on="on" @click="openUrl(item.url || item.data, '_blank')">
v-else-if="item.icon" {{ item.icon }}
:size="active ? 33 : 22"
v-on="on"
@click="openUrl(item.url || item.data,'_blank')"
>
{{
item.icon
}}
</v-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 mdi-file
</v-icon> </v-icon>
</template> </template>
@ -63,75 +225,47 @@
</div> </div>
</div> </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" class="add d-flex align-center justify-center px-1 nc-attachment-add"
@click="addFile" @click="addFile"
> >
<v-icon v-if="uploading" small color="primary" class="nc-attachment-add-spinner"> <v-icon v-if="uploading" small color="primary" class="nc-attachment-add-spinner"> mdi-loading mdi-spin </v-icon>
mdi-loading mdi-spin <v-btn v-else-if="isForm" outlined x-small color="" text class="nc-attachment-add-btn">
</v-icon> <v-icon x-small color=""> mdi-plus </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 Attachment
</v-btn> </v-btn>
<v-icon <v-icon v-else-if="_isUIAllowed('tableAttachment')" v-show="active" small color="primary nc-attachment-add-icon">
v-else-if="_isUIAllowed('tableAttachment')"
v-show="active"
small
color="primary nc-attachment-add-icon"
>
mdi-plus mdi-plus
</v-icon> </v-icon>
</div> </div>
<v-spacer /> <v-spacer />
<v-icon class="expand-icon mr-1" x-small color="primary" @click.stop="dialog = true"> <v-icon class="expand-icon mr-1" x-small color="primary" @click.stop="dialog = true"> mdi-arrow-expand </v-icon>
mdi-arrow-expand <input ref="file" type="file" multiple class="d-none" @change="onFileSelection" />
</v-icon>
<input ref="file" type="file" multiple class="d-none" @change="onFileSelection">
</div> </div>
<v-dialog <v-dialog v-if="dialog" v-model="dialog" width="800">
v-if="dialog"
v-model="dialog"
width="800"
>
<v-card class="h-100 images-modal"> <v-card class="h-100 images-modal">
<v-card-text class="h-100 backgroundColor"> <v-card-text class="h-100 backgroundColor">
<div class="d-flex mx-2"> <div class="d-flex mx-2">
<v-btn <v-btn
v-if="(isForm || _isUIAllowed('tableAttachment')) && !isPublicGrid && !isLocked" v-if="(isForm || _isUIAllowed('tableAttachment')) && !isPublicGrid && !isLocked"
small small
class="my-4 " class="my-4"
:loading="uploading" :loading="uploading"
@click="addFile" @click="addFile"
> >
<v-icon small class="mr-2"> <v-icon small class="mr-2"> mdi-link-variant </v-icon>
mdi-link-variant
</v-icon>
<span class="caption">Attach File</span> <span class="caption">Attach File</span>
</v-btn> </v-btn>
<!-- <v-text-field v-model="urlString" @keypress.enter="uploadByUrl" />--> <!-- <v-text-field v-model="urlString" @keypress.enter="uploadByUrl" /> -->
</div> </div>
<div class="d-flex flex-wrap h-100"> <div class="d-flex flex-wrap h-100">
<v-container fluid style="max-height:calc(90vh - 80px);overflow-y: auto"> <v-container fluid style="max-height: calc(90vh - 80px); overflow-y: auto">
<draggable <Draggable v-model="localState" class="row" @update="onOrderUpdate">
v-model="localState" <v-col v-for="(item, i) in isPublicForm ? localFilesState : localState" :key="i" cols="4">
class="row"
@update="onOrderUpdate"
>
<v-col v-for="(item,i) in (isPublicForm ? localFilesState : localState)" :key="i" cols="4">
<v-card <v-card
class="modal-thumbnail-card align-center justify-center d-flex" class="modal-thumbnail-card align-center justify-center d-flex"
height="200px" height="200px"
@ -145,33 +279,27 @@
> >
mdi-close-circle mdi-close-circle
</v-icon> </v-icon>
<v-icon color="grey" class="download-icon" @click.stop="downloadItem(item,i)"> <v-icon color="grey" class="download-icon" @click.stop="downloadItem(item, i)"> mdi-download </v-icon>
mdi-download <div class="pa-2 d-flex align-center" style="height: 200px">
</v-icon>
<div class="pa-2 d-flex align-center" style="height:200px">
<img <img
v-if="isImage(item.title, item.mimetype)" v-if="isImage(item.title, item.mimetype)"
style="max-height: 100%;max-width: 100%" style="max-height: 100%; max-width: 100%"
alt="#" alt="#"
:src="item.url || item.data" :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')"> <v-icon v-else-if="item.icon" size="33" @click="openUrl(item.url || item.data, '_blank')">
{{ {{ item.icon }}
item.icon
}}
</v-icon>
<v-icon v-else size="33" @click="openUrl(item.url || item.data,'_blank')">
mdi-file
</v-icon> </v-icon>
<v-icon v-else size="33" @click="openUrl(item.url || item.data, '_blank')"> mdi-file </v-icon>
</div> </div>
</v-card> </v-card>
<p class="caption mt-2 modal-title" :title="item.title"> <p class="caption mt-2 modal-title" :title="item.title">
{{ item.title }} {{ item.title }}
</p> </p>
</v-col> </v-col>
</draggable> </Draggable>
</v-container> </v-container>
</div> </div>
</v-card-text> </v-card-text>
@ -182,29 +310,22 @@
<div v-click-outside="hideIfVisible" class="image-overlay-container"> <div v-click-outside="hideIfVisible" class="image-overlay-container">
<template v-if="showImage && selectedImage"> <template v-if="showImage && selectedImage">
<v-carousel v-model="carousel" height="calc(100vh - 100px)" hide-delimiters> <v-carousel v-model="carousel" height="calc(100vh - 100px)" hide-delimiters>
<v-carousel-item <v-carousel-item v-for="(item, i) in isPublicForm ? localFilesState : localState" :key="i">
v-for="(item,i) in (isPublicForm ? localFilesState : localState)" <div class="mx-auto d-flex flex-column justify-center align-center" style="min-height: 100px">
:key="i"
>
<div class="mx-auto d-flex flex-column justify-center align-center" style="min-height:100px">
<p class="title text-center"> <p class="title text-center">
{{ item.title }} {{ item.title }}
<v-icon class="ml-3" color="grey" @click.stop="downloadItem(item,i)"> <v-icon class="ml-3" color="grey" @click.stop="downloadItem(item, i)"> mdi-download </v-icon>
mdi-download
</v-icon>
</p> </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 <img
v-if="isImage(item.title, item.mimetype)" 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" :src="item.url || item.data"
> />
<v-icon v-else-if="item.icon" size="55"> <v-icon v-else-if="item.icon" size="55">
{{ item.icon }} {{ item.icon }}
</v-icon> </v-icon>
<v-icon v-else size="55"> <v-icon v-else size="55"> mdi-file </v-icon>
mdi-file
</v-icon>
</div> </div>
</div> </div>
</v-carousel-item> </v-carousel-item>
@ -217,210 +338,35 @@
height="80px" height="80px"
style="background: transparent" style="background: transparent"
> >
<v-slide-group <v-slide-group multiple show-arrows>
multiple <v-slide-item v-for="(item, i) in isPublicForm ? localFilesState : localState" :key="i">
show-arrows
>
<v-slide-item
v-for="(item,i) in (isPublicForm ? localFilesState : localState)"
:key="i"
>
<v-card <v-card
:key="i" :key="i"
class="ma-2 pa-2 d-flex align-center justify-center overlay-thumbnail" class="ma-2 pa-2 d-flex align-center justify-center overlay-thumbnail"
:class="{active: carousel === i}" :class="{ active: carousel === i }"
width="48" width="48"
height="48" height="48"
@click="carousel = i" @click="carousel = i"
> >
<img <img
v-if="isImage(item.title, item.mimetype)" 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" :src="item.url || item.data"
> />
<v-icon v-else-if="item.icon" size="48"> <v-icon v-else-if="item.icon" size="48">
{{ item.icon }} {{ item.icon }}
</v-icon> </v-icon>
<v-icon v-else size="48"> <v-icon v-else size="48"> mdi-file </v-icon>
mdi-file
</v-icon>
</v-card> </v-card>
</v-slide-item> </v-slide-item>
</v-slide-group> </v-slide-group>
</v-sheet> </v-sheet>
<v-icon x-large class="close-icon" @click="showImage=false"> <v-icon x-large class="close-icon" @click="showImage = false"> mdi-close-circle </v-icon>
mdi-close-circle
</v-icon>
</div> </div>
</v-overlay> </v-overlay>
</div> </div>
</template> </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"> <style scoped lang="scss">
.img-container { .img-container {
margin: 0 -2px; margin: 0 -2px;
@ -431,7 +377,7 @@ export default {
} }
.add { .add {
transition: .2s background-color; transition: 0.2s background-color;
/*background-color: #666666ee;*/ /*background-color: #666666ee;*/
border-radius: 4px; border-radius: 4px;
height: 33px; height: 33px;
@ -464,7 +410,7 @@ export default {
margin-left: 8px; margin-left: 8px;
border-radius: 2px; border-radius: 2px;
/*opacity: 0;*/ /*opacity: 0;*/
transition: .3s background-color; transition: 0.3s background-color;
} }
.expand-icon:hover { .expand-icon:hover {
@ -476,7 +422,6 @@ export default {
height: 50px; height: 50px;
max-width: 100%; max-width: 100%;
border-radius: 4px; border-radius: 4px;
} }
.modal-thumbnail { .modal-thumbnail {
@ -487,21 +432,20 @@ export default {
.remove-icon { .remove-icon {
position: absolute; position: absolute;
top: 5px; top: 5px;
right: 5px right: 5px;
} }
.modal-thumbnail-card { .modal-thumbnail-card {
.download-icon { .download-icon {
position: absolute; position: absolute;
bottom: 5px; bottom: 5px;
right: 5px; right: 5px;
opacity: 0; opacity: 0;
transition: .4s opacity; transition: 0.4s opacity;
} }
&:hover .download-icon { &:hover .download-icon {
opacity: 1 opacity: 1;
} }
} }
@ -514,12 +458,12 @@ export default {
.image-overlay-container .close-icon { .image-overlay-container .close-icon {
position: fixed; position: fixed;
top: 15px; top: 15px;
right: 15px right: 15px;
} }
.overlay-thumbnail { .overlay-thumbnail {
transition: .4s transform, .4s opacity; transition: 0.4s transform, 0.4s opacity;
opacity: .5; opacity: 0.5;
} }
.overlay-thumbnail.active { .overlay-thumbnail.active {
@ -539,7 +483,7 @@ export default {
} }
.modal-thumbnail-card { .modal-thumbnail-card {
transition: .4s transform; transition: 0.4s transform;
} }
.modal-thumbnail-card:hover { .modal-thumbnail-card:hover {
@ -564,13 +508,12 @@ export default {
.expand-icon { .expand-icon {
opacity: 0; opacity: 0;
transition: .4s opacity; transition: 0.4s opacity;
} }
.main:hover .expand-icon { .main:hover .expand-icon {
opacity: 1; opacity: 1;
} }
</style> </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> <script>
import { isValidURL } from '@/helpers' import { isValidURL } from '@/helpers'
@ -9,7 +5,7 @@ export default {
name: 'EditableUrlCell', name: 'EditableUrlCell',
props: { props: {
value: String, value: String,
column: Object column: Object,
}, },
computed: { computed: {
localState: { localState: {
@ -17,12 +13,10 @@ export default {
return this.value return this.value
}, },
set(val) { set(val) {
if (!( if (!(this.column && this.column.meta && this.column.meta.validate) || isValidURL(val)) {
this.column && this.$emit('input', val)
this.column.meta && }
this.column.meta.validate },
) || isValidURL(val)) { this.$emit('input', val) }
}
}, },
parentListeners() { parentListeners() {
const $listeners = {} const $listeners = {}
@ -39,16 +33,21 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
this.$el.focus() this.$el.focus()
} },
} }
</script> </script>
<template>
<input v-model="localState" v-on="parentListeners" />
</template>
<style scoped> <style scoped>
input, textarea { input,
textarea {
width: 100%; width: 100%;
height: 100%; height: 100%;
color: var(--v-textColor-base); 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> <script>
import colors from '@/mixins/colors' import colors from '@/mixins/colors'
@ -45,22 +8,20 @@ export default {
props: { props: {
value: String, value: String,
column: Object, column: Object,
isForm: Boolean isForm: Boolean,
}, },
computed: { computed: {
localState: { localState: {
get() { get() {
return this.value && this.value.replace(/\\'/g, '\'').replace(/^'|'$/g, '') return this.value && this.value.replace(/\\'/g, "'").replace(/^'|'$/g, '')
}, },
set(val) { set(val) {
this.$emit('input', val) this.$emit('input', val)
} },
}, },
enumValues() { enumValues() {
if (this.column && this.column.dtxp) { if (this.column && this.column.dtxp) {
return this.column.dtxp return this.column.dtxp.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, ''))
.split(',')
.map(v => v.replace(/\\'/g, '\'').replace(/^'|'$/g, ''))
} }
return [] return []
}, },
@ -75,7 +36,7 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
// this.$el.focus(); // this.$el.focus();
@ -83,16 +44,51 @@ export default {
// event = document.createEvent('MouseEvents'); // event = document.createEvent('MouseEvents');
// event.initMouseEvent('mousedown', true, true, window); // event.initMouseEvent('mousedown', true, true, window);
// this.$el.dispatchEvent(event); // this.$el.dispatchEvent(event);
} },
} }
</script> </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"> <style scoped lang="scss">
::v-deep { ::v-deep {
.v-select { .v-select {
min-width: 150px; min-width: 150px;
} }
.v-input__slot{ .v-input__slot {
padding-right: 0 !important; padding-right: 0 !important;
padding-left: 35px !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> <script>
import { enumColor as colors } from '@/components/project/spreadsheet/helpers/colors' import { enumColor as colors } from '@/components/project/spreadsheet/helpers/colors'
@ -22,7 +5,7 @@ export default {
name: 'EnumRadioEditableCell', name: 'EnumRadioEditableCell',
props: { props: {
value: String, value: String,
column: Object column: Object,
}, },
computed: { computed: {
colors() { colors() {
@ -35,11 +18,11 @@ export default {
set(val) { set(val) {
this.$emit('input', val) this.$emit('input', val)
this.$emit('update') this.$emit('update')
} },
}, },
enumValues() { enumValues() {
if (this.column && this.column.dtxp) { 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 [] return []
}, },
@ -53,7 +36,7 @@ export default {
$listeners.focus = this.$listeners.focus $listeners.focus = this.$listeners.focus
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
// this.$el.focus(); // this.$el.focus();
@ -61,12 +44,29 @@ export default {
// event = document.createEvent('MouseEvents'); // event = document.createEvent('MouseEvents');
// event.initMouseEvent('mousedown', true, true, window); // event.initMouseEvent('mousedown', true, true, window);
// this.$el.dispatchEvent(event); // this.$el.dispatchEvent(event);
} },
} }
</script> </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 { .label {
border-radius: 25px; 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> <script>
export default { export default {
name: 'FloatCell', name: 'FloatCell',
props: { props: {
value: [String, Number] value: [String, Number],
}, },
computed: { computed: {
localState: { localState: {
@ -15,7 +11,7 @@ export default {
}, },
set(val) { set(val) {
this.$emit('input', +val) this.$emit('input', +val)
} },
}, },
parentListeners() { parentListeners() {
const $listeners = {} const $listeners = {}
@ -28,14 +24,18 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
this.$el.focus() this.$el.focus()
} },
} }
</script> </script>
<template>
<input v-model="localState" type="number" v-on="parentListeners" />
</template>
<style scoped> <style scoped>
input { input {
width: 100%; 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> <script>
export default { export default {
name: 'IntegerCell', name: 'IntegerCell',
props: { props: {
value: [String, Number] value: [String, Number],
}, },
computed: { computed: {
localState: { localState: {
@ -15,7 +11,7 @@ export default {
}, },
set(val) { set(val) {
this.$emit('input', parseInt(val, 10)) this.$emit('input', parseInt(val, 10))
} },
}, },
parentListeners() { parentListeners() {
const $listeners = {} const $listeners = {}
@ -28,14 +24,18 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
this.$el.focus() this.$el.focus()
} },
} }
</script> </script>
<template>
<input v-model="localState" type="number" v-on="parentListeners" />
</template>
<style scoped> <style scoped>
input { input {
width: 100%; 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> <script>
import MonacoJsonObjectEditor from '@/components/monaco/MonacoJsonObjectEditor' import MonacoJsonObjectEditor from '@/components/monaco/MonacoJsonObjectEditor'
@ -48,16 +6,15 @@ export default {
components: { MonacoJsonObjectEditor }, components: { MonacoJsonObjectEditor },
props: { props: {
value: [String, Object], value: [String, Object],
isForm: Boolean isForm: Boolean,
}, },
data: () => ({ data: () => ({
localState: '', localState: '',
expand: false, expand: false,
isValid: true, isValid: true,
error: undefined error: undefined,
}), }),
computed: { computed: {
parentListeners() { parentListeners() {
const $listeners = {} const $listeners = {}
@ -69,7 +26,7 @@ export default {
} }
return $listeners return $listeners
} },
}, },
watch: { watch: {
value(val) { value(val) {
@ -83,7 +40,7 @@ export default {
if (this.isForm) { if (this.isForm) {
this.$emit('input', JSON.stringify(val)) this.$emit('input', JSON.stringify(val))
} }
} },
}, },
created() { created() {
try { try {
@ -92,8 +49,7 @@ export default {
// ignore parse error for invalid JSON // ignore parse error for invalid JSON
} }
}, },
mounted() { mounted() {},
},
methods: { methods: {
save() { save() {
this.expand = false this.expand = false
@ -102,14 +58,56 @@ export default {
validate(n, e) { validate(n, e) {
this.isValid = n this.isValid = n
this.error = e this.error = e
} },
} },
} }
</script> </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> <style scoped>
.cell-container { .cell-container {
width: 100% width: 100%;
} }
</style> </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> <script>
export default { export default {
name: 'RatingCell', name: 'RatingCell',
props: { props: {
column: Object, column: Object,
value: [String, Number], value: [String, Number],
readOnly: Boolean readOnly: Boolean,
}, },
computed: { computed: {
fullIcon() { fullIcon() {
@ -47,25 +19,36 @@ export default {
}, },
set(val) { set(val) {
this.$emit('input', val) this.$emit('input', val)
} },
}, },
ratingMeta() { ratingMeta() {
return { return {
icon: { icon: {
full: 'mdi-star', full: 'mdi-star',
empty: 'mdi-star-outline' empty: 'mdi-star-outline',
}, },
color: '#fcb401', color: '#fcb401',
max: 5, max: 5,
...(this.column && this.column.meta ...(this.column && this.column.meta ? this.column.meta : {}),
? this.column.meta
: {})
} }
} },
} },
} }
</script> </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> <script>
import colors from '@/components/project/spreadsheet/helpers/colors' import colors from '@/components/project/spreadsheet/helpers/colors'
@ -23,10 +6,9 @@ export default {
props: { props: {
value: String, value: String,
column: Object, column: Object,
values: Array values: Array,
},
data() {
}, },
data() {},
computed: { computed: {
colors() { colors() {
return this.$store.state.settings.darkTheme ? colors.dark : colors.light return this.$store.state.settings.darkTheme ? colors.dark : colors.light
@ -38,11 +20,11 @@ export default {
set(val) { set(val) {
this.$emit('input', val.join(',')) this.$emit('input', val.join(','))
this.$emit('update') this.$emit('update')
} },
}, },
setValues() { setValues() {
if (this.column && this.column.dtxp) { 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 || [] return this.values || []
}, },
@ -57,23 +39,39 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
this.$el.focus() this.$el.focus()
const event = document.createEvent('MouseEvents') const event = document.createEvent('MouseEvents')
event.initMouseEvent('mousedown', true, true, window) event.initMouseEvent('mousedown', true, true, window)
this.$el.dispatchEvent(event) this.$el.dispatchEvent(event)
} },
} }
</script> </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 { .label {
border-radius: 25px; border-radius: 25px;
} }
</style> </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> <script>
import colors from '@/mixins/colors' import colors from '@/mixins/colors'
@ -46,24 +6,20 @@ export default {
mixins: [colors], mixins: [colors],
props: { props: {
value: String, value: String,
column: Object column: Object,
}, },
computed: { computed: {
localState: { localState: {
get() { get() {
return this.value && this.value return this.value && this.value.match(/(?:[^',]|\\')+(?='?(?:,|$))/g).map((v) => v.replace(/\\'/g, "'"))
.match(/(?:[^',]|\\')+(?='?(?:,|$))/g)
.map(v => v.replace(/\\'/g, '\''))
}, },
set(val) { 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() { setValues() {
if (this.column && this.column.dtxp) { if (this.column && this.column.dtxp) {
return this.column.dtxp return this.column.dtxp.match(/(?:[^']|\\')+(?='?(?:,|$))/g).map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, ''))
.match(/(?:[^']|\\')+(?='?(?:,|$))/g)
.map(v => v.replace(/\\'/g, '\'').replace(/^'|'$/g, ''))
} }
return [] return []
}, },
@ -78,7 +34,7 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
// this.$el.focus(); // this.$el.focus();
@ -86,10 +42,48 @@ export default {
// event = document.createEvent('MouseEvents'); // event = document.createEvent('MouseEvents');
// event.initMouseEvent('mousedown', true, true, window); // event.initMouseEvent('mousedown', true, true, window);
// this.$el.dispatchEvent(event); // this.$el.dispatchEvent(event);
} },
} }
</script> </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> <style scoped>
select { select {
width: 100%; width: 100%;
@ -101,7 +95,6 @@ select {
/*Firefox */ /*Firefox */
appearance: menulist; appearance: menulist;
} }
</style> </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> <script>
export default { export default {
name: 'TextAreaCell', name: 'TextAreaCell',
props: { props: {
value: String value: String,
}, },
computed: { computed: {
localState: { localState: {
get() { get() {
return this.value return this.value
}, },
set(val) { set(val) {
this.$emit('input', val) this.$emit('input', val)
} },
}, },
parentListeners() { parentListeners() {
const $listeners = {} const $listeners = {}
@ -36,19 +24,31 @@ export default {
} }
return $listeners return $listeners
} },
}, },
created() { created() {
this.localState = this.value this.localState = this.value
}, },
mounted() { mounted() {
this.$refs.textarea && this.$refs.textarea.focus() this.$refs.textarea && this.$refs.textarea.focus()
} },
} }
</script> </script>
<template>
<textarea
ref="textarea"
v-model="localState"
rows="4"
v-on="parentListeners"
@keydown.alt.enter.stop
@keydown.shift.enter.stop
/>
</template>
<style scoped> <style scoped>
input, textarea { input,
textarea {
width: 100%; width: 100%;
min-height: 60px; min-height: 60px;
height: 100%; 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> <script>
export default { export default {
name: 'TextAreaCell', name: 'TextAreaCell',
props: { props: {
value: String, value: String,
isForm: Boolean isForm: Boolean,
}, },
data: () => ({ data: () => ({
localState: '' localState: '',
}), }),
computed: { computed: {
parentListeners() { parentListeners() {
const $listeners = {} const $listeners = {}
@ -45,7 +20,7 @@ export default {
} }
return $listeners return $listeners
} },
}, },
watch: { watch: {
value(val) { value(val) {
@ -55,7 +30,7 @@ export default {
if (this.isForm) { if (this.isForm) {
this.$emit('input', val) this.$emit('input', val)
} }
} },
}, },
created() { created() {
this.localState = this.value this.localState = this.value
@ -66,15 +41,33 @@ export default {
methods: { methods: {
save() { save() {
this.$emit('input', this.localState) this.$emit('input', this.localState)
} },
} },
} }
</script> </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> <style scoped>
input, textarea { input,
textarea {
width: 100%; width: 100%;
min-height:60px; min-height: 60px;
height: calc(100% - 28px); height: calc(100% - 28px);
color: var(--v-textColor-base); 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> <script>
export default { export default {
name: 'TextCell', name: 'TextCell',
props: { props: {
value: [String, Object, Number, Boolean, Array] value: [String, Object, Number, Boolean, Array],
}, },
computed: { computed: {
localState: { localState: {
@ -15,7 +11,7 @@ export default {
}, },
set(val) { set(val) {
this.$emit('input', val) this.$emit('input', val)
} },
}, },
parentListeners() { parentListeners() {
const $listeners = {} const $listeners = {}
@ -32,16 +28,21 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
this.$el.focus() this.$el.focus()
} },
} }
</script> </script>
<template>
<input v-model="localState" v-on="parentListeners" />
</template>
<style scoped> <style scoped>
input, textarea { input,
textarea {
width: 100%; width: 100%;
height: 100%; height: 100%;
color: var(--v-textColor-base); 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> <script>
import dayjs from 'dayjs' import dayjs from 'dayjs'
export default { export default {
name: 'TimePickerCell', name: 'TimePickerCell',
props: { props: {
value: [String, Date] value: [String, Date],
}, },
computed: { computed: {
isMysql() { isMysql() {
@ -51,8 +36,7 @@ export default {
this.$emit('input', dateTime.format('YYYY-MM-DD HH:mm:ssZ')) this.$emit('input', dateTime.format('YYYY-MM-DD HH:mm:ssZ'))
} }
} }
} },
}, },
parentListeners() { parentListeners() {
const $listeners = {} const $listeners = {}
@ -69,16 +53,31 @@ export default {
} }
return $listeners return $listeners
} },
}, },
mounted() { mounted() {
if (this.$el && this.$el.$el) { if (this.$el && this.$el.$el) {
this.$el.$el.focus() this.$el.$el.focus()
} }
} },
} }
</script> </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> <style scoped>
.value { .value {
width: 100%; width: 100%;

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

@ -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 useColumn from '~/composables/useColumn' import useColumn from '~/composables/useColumn'
const { column, value } = defineProps<{ column: ColumnType; value: any }>() const { column, value } = defineProps<{ column: ColumnType; value: any }>()
provide('column', column) provide('column', column)
@ -38,11 +38,7 @@ const {
<!-- <CellDuration v-else-if="isDuration" /> --> <!-- <CellDuration v-else-if="isDuration" /> -->
<!-- <CellRating v-else-if="isRating" /> --> <!-- <CellRating v-else-if="isRating" /> -->
<!-- <CellCurrency v-else-if="isCurrency" /> --> <!-- <CellCurrency v-else-if="isCurrency" /> -->
<span <span v-else :title="title">{{ value }}</span>
v-else
:title="title"
>{{ value }}</span>
</template> </template>
<style scoped> <style scoped></style>
</style>

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

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

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

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

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

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

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

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

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

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

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

@ -1,23 +1,23 @@
import type { ProjectType, TableType } from "nocodb-sdk"; import type { ProjectType, TableType } from 'nocodb-sdk'
import { useNuxtApp } from "#app"; import { useNuxtApp } from '#app'
export default () => { export default () => {
const { $api } = useNuxtApp(); const { $api } = useNuxtApp()
const project = useState<ProjectType>("project"); const project = useState<ProjectType>('project')
const tables = useState<Array<TableType>>("tables"); const tables = useState<Array<TableType>>('tables')
const loadTables = async () => { const loadTables = async () => {
if (project.value.id) { 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) => { 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 { Api, PaginatedType, TableType } from 'nocodb-sdk'
import type { ComputedRef, Ref } from "vue"; import type { ComputedRef, Ref } from 'vue'
import type { PaginatedType, TableType } from "nocodb-sdk"; import { useNuxtApp } from '#app'
import { useNuxtApp } from "#app"; import useProject from '~/composables/useProject'
import useProject from "~/composables/useProject";
const formatData = (list: Array<Record<string, any>>) => list.map(row => ({ const formatData = (list: Array<Record<string, any>>) =>
row: { ...row }, list.map((row) => ({
oldRow: { ...row }, row: { ...row },
rowMeta: {} oldRow: { ...row },
})); rowMeta: {},
}))
export default (meta: Ref<TableType> | ComputedRef<TableType> | undefined) => { export default (meta: Ref<TableType> | ComputedRef<TableType> | undefined) => {
const data = ref<Array<Record<string, any>>>(); const data = ref<Array<Record<string, any>>>()
const formattedData = ref<Array<{ row: Record<string, any>; oldRow: Record<string, any> }>>(); const formattedData = ref<Array<{ row: Record<string, any>; oldRow: Record<string, any> }>>()
const paginationData = ref<PaginatedType>(); const paginationData = ref<PaginatedType>()
const { project } = useProject(); const { project } = useProject()
const { $api } = useNuxtApp(); const { $api } = useNuxtApp()
const loadData = async (params: Parameters<Api<any>["dbTableRow"]["list"]>[3] = {}) => { const loadData = async (params: Parameters<Api<any>['dbTableRow']['list']>[3] = {}) => {
if(!project?.value?.id || !meta?.value?.id) return if (!project?.value?.id || !meta?.value?.id) return
const response = await $api.dbTableRow.list( const response = await $api.dbTableRow.list('noco', project.value.id, meta.value.id, params)
"noco", data.value = response.list
project.value.id, formattedData.value = formatData(response.list)
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"> <script setup lang="ts">
import { watch } from "vue"; import { watch } from 'vue'
import useProject from "~/composables/useProject"; import useProject from '~/composables/useProject'
import useTabs from "~/composables/useTabs"; import useTabs from '~/composables/useTabs'
const route = useRoute()
const route = useRoute(); const { loadProject, loadTables } = useProject()
const { loadProject, loadTables } = useProject(); const { clearTabs } = useTabs()
const { clearTabs } = useTabs();
onMounted(async () => { onMounted(async () => {
await loadProject(route.params.projectId as string); await loadProject(route.params.projectId as string)
await loadTables(); await loadTables()
}); })
watch( watch(
() => route.params.projectId, () => route.params.projectId,
async (newVal, oldVal) => { async (newVal, oldVal) => {
if (newVal && newVal !== oldVal) { if (newVal && newVal !== oldVal) {
clearTabs(); clearTabs()
await loadProject(newVal as string); await loadProject(newVal as string)
await loadTables(); await loadTables()
} }
} },
); )
</script> </script>
<template> <template>
@ -33,10 +32,10 @@ watch(
</v-navigation-drawer> </v-navigation-drawer>
</template> </template>
<v-container> <v-container>
<DashboardTabView /> <DashboardTabView />
</v-container> </v-container>
</NuxtLayout> </NuxtLayout>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

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

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

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

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

Loading…
Cancel
Save