Browse Source

Merge pull request #4908 from nocodb/feat/text-null

feat: visual distinction between NULL and empty string
pull/4930/head
Raju Udava 2 years ago committed by GitHub
parent
commit
002d317ef0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      packages/nc-gui/assets/style.scss
  2. 4
      packages/nc-gui/components/cell/Currency.vue
  3. 5
      packages/nc-gui/components/cell/DatePicker.vue
  4. 7
      packages/nc-gui/components/cell/DateTimePicker.vue
  5. 3
      packages/nc-gui/components/cell/Decimal.vue
  6. 4
      packages/nc-gui/components/cell/Duration.vue
  7. 4
      packages/nc-gui/components/cell/Email.vue
  8. 3
      packages/nc-gui/components/cell/Float.vue
  9. 3
      packages/nc-gui/components/cell/Integer.vue
  10. 4
      packages/nc-gui/components/cell/Json.vue
  11. 3
      packages/nc-gui/components/cell/Percent.vue
  12. 4
      packages/nc-gui/components/cell/Text.vue
  13. 4
      packages/nc-gui/components/cell/TextArea.vue
  14. 7
      packages/nc-gui/components/cell/TimePicker.vue
  15. 4
      packages/nc-gui/components/cell/Url.vue
  16. 5
      packages/nc-gui/components/cell/YearPicker.vue
  17. 6
      packages/nc-gui/components/dashboard/settings/Misc.vue
  18. 1
      packages/nc-gui/composables/useGlobal/state.ts
  19. 1
      packages/nc-gui/composables/useGlobal/types.ts
  20. 2
      tests/playwright/pages/Dashboard/Settings/Miscellaneous.ts

7
packages/nc-gui/assets/style.scss

@ -294,3 +294,10 @@ a {
.ant-select-selection-search-input:focus { .ant-select-selection-search-input:focus {
@apply !ring-0; @apply !ring-0;
} }
.nc-null {
@apply text-gray-300 italic;
input::placeholder {
@apply text-gray-300 italic;
}
}

4
packages/nc-gui/components/cell/Currency.vue

@ -10,6 +10,8 @@ const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'save']) const emit = defineEmits(['update:modelValue', 'save'])
const { showNull } = useGlobal()
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
const editEnabled = inject(EditModeInj)! const editEnabled = inject(EditModeInj)!
@ -81,6 +83,8 @@ onMounted(() => {
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel">{{ currency }}</span> <span v-else-if="vModel">{{ currency }}</span>
<span v-else /> <span v-else />

5
packages/nc-gui/components/cell/DatePicker.vue

@ -22,6 +22,8 @@ const { modelValue, isPk } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { showNull } = useGlobal()
const columnMeta = inject(ColumnInj, null)! const columnMeta = inject(ColumnInj, null)!
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
@ -71,7 +73,7 @@ watch(
{ flush: 'post' }, { flush: 'post' },
) )
const placeholder = computed(() => (isDateInvalid ? 'Invalid date' : '')) const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isDateInvalid ? 'Invalid date' : ''))
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
@ -169,6 +171,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
v-model:value="localState" v-model:value="localState"
:bordered="false" :bordered="false"
class="!w-full !px-0 !border-none" class="!w-full !px-0 !border-none"
:class="{ 'nc-null': modelValue === null && showNull }"
:format="dateFormat" :format="dateFormat"
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"

7
packages/nc-gui/components/cell/DateTimePicker.vue

@ -25,6 +25,8 @@ const emit = defineEmits(['update:modelValue'])
const { isMysql } = useProject() const { isMysql } = useProject()
const { showNull } = useGlobal()
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
const active = inject(ActiveCellInj, ref(false)) const active = inject(ActiveCellInj, ref(false))
@ -79,6 +81,8 @@ watch(
{ flush: 'post' }, { flush: 'post' },
) )
const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isDateInvalid ? 'Invalid date' : ''))
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
case 'Enter': case 'Enter':
@ -172,8 +176,9 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
:show-time="true" :show-time="true"
:bordered="false" :bordered="false"
class="!w-full !px-0 !border-none" class="!w-full !px-0 !border-none"
:class="{ 'nc-null': modelValue === null && showNull }"
:format="dateTimeFormat" :format="dateTimeFormat"
:placeholder="isDateInvalid ? 'Invalid date' : ''" :placeholder="placeholder"
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true" :input-read-only="true"
:dropdown-class-name="`${randomClass} nc-picker-datetime ${open ? 'active' : ''}`" :dropdown-class-name="`${randomClass} nc-picker-datetime ${open ? 'active' : ''}`"

3
packages/nc-gui/components/cell/Decimal.vue

@ -14,6 +14,8 @@ const props = defineProps<Props>()
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
const { showNull } = useGlobal()
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const _vModel = useVModel(props, 'modelValue', emits) const _vModel = useVModel(props, 'modelValue', emits)
@ -49,6 +51,7 @@ const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else class="text-sm">{{ vModel }}</span> <span v-else class="text-sm">{{ vModel }}</span>
</template> </template>

4
packages/nc-gui/components/cell/Duration.vue

@ -19,6 +19,8 @@ const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { showNull } = useGlobal()
const column = inject(ColumnInj) const column = inject(ColumnInj)
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
@ -93,6 +95,8 @@ const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="modelValue === null && showNull" class="nc-null">NULL</span>
<span v-else> {{ localState }}</span> <span v-else> {{ localState }}</span>
<div v-if="showWarningMessage" class="duration-warning"> <div v-if="showWarningMessage" class="duration-warning">

4
packages/nc-gui/components/cell/Email.vue

@ -14,6 +14,8 @@ const props = defineProps<Props>()
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
const { showNull } = useGlobal()
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const vModel = useVModel(props, 'modelValue', emits) const vModel = useVModel(props, 'modelValue', emits)
@ -39,6 +41,8 @@ const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<a v-else-if="validEmail" class="text-sm underline hover:opacity-75" :href="`mailto:${vModel}`" target="_blank"> <a v-else-if="validEmail" class="text-sm underline hover:opacity-75" :href="`mailto:${vModel}`" target="_blank">
{{ vModel }} {{ vModel }}
</a> </a>

3
packages/nc-gui/components/cell/Float.vue

@ -14,6 +14,8 @@ const props = defineProps<Props>()
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
const { showNull } = useGlobal()
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const _vModel = useVModel(props, 'modelValue', emits) const _vModel = useVModel(props, 'modelValue', emits)
@ -49,6 +51,7 @@ const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else class="text-sm">{{ vModel }}</span> <span v-else class="text-sm">{{ vModel }}</span>
</template> </template>

3
packages/nc-gui/components/cell/Integer.vue

@ -14,6 +14,8 @@ const props = defineProps<Props>()
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
const { showNull } = useGlobal()
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const _vModel = useVModel(props, 'modelValue', emits) const _vModel = useVModel(props, 'modelValue', emits)
@ -53,6 +55,7 @@ function onKeyDown(evt: KeyboardEvent) {
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else class="text-sm">{{ vModel }}</span> <span v-else class="text-sm">{{ vModel }}</span>
</template> </template>

4
packages/nc-gui/components/cell/Json.vue

@ -25,6 +25,8 @@ const props = defineProps<Props>()
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
const { showNull } = useGlobal()
const editEnabled = inject(EditModeInj, ref(false)) const editEnabled = inject(EditModeInj, ref(false))
const active = inject(ActiveCellInj, ref(false)) const active = inject(ActiveCellInj, ref(false))
@ -154,6 +156,8 @@ useSelectedCellKeyupListener(active, (e) => {
</span> </span>
</div> </div>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else>{{ vModel }}</span> <span v-else>{{ vModel }}</span>
</component> </component>
</template> </template>

3
packages/nc-gui/components/cell/Percent.vue

@ -10,6 +10,8 @@ const props = defineProps<Props>()
const emits = defineEmits(['update:modelValue']) const emits = defineEmits(['update:modelValue'])
const { showNull } = useGlobal()
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const _vModel = useVModel(props, 'modelValue', emits) const _vModel = useVModel(props, 'modelValue', emits)
@ -47,5 +49,6 @@ const focus: VNodeRef = (el) => {
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else>{{ vModel }}</span> <span v-else>{{ vModel }}</span>
</template> </template>

4
packages/nc-gui/components/cell/Text.vue

@ -10,6 +10,8 @@ const props = defineProps<Props>()
const emits = defineEmits(['update:modelValue']) const emits = defineEmits(['update:modelValue'])
const { showNull } = useGlobal()
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const readonly = inject(ReadonlyInj, ref(false)) const readonly = inject(ReadonlyInj, ref(false))
@ -38,5 +40,7 @@ const focus: VNodeRef = (el) => {
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<LazyCellClampedText v-else :value="vModel" :lines="1" /> <LazyCellClampedText v-else :value="vModel" :lines="1" />
</template> </template>

4
packages/nc-gui/components/cell/TextArea.vue

@ -11,6 +11,8 @@ const emits = defineEmits(['update:modelValue'])
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const { showNull } = useGlobal()
const view = inject(ActiveViewInj, ref()) const view = inject(ActiveViewInj, ref())
const vModel = useVModel(props, 'modelValue', emits, { defaultValue: '' }) const vModel = useVModel(props, 'modelValue', emits, { defaultValue: '' })
@ -55,6 +57,8 @@ const rowHeight = computed(() => {
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<LazyCellClampedText v-else-if="rowHeight" :value="vModel" :lines="rowHeight" /> <LazyCellClampedText v-else-if="rowHeight" :value="vModel" :lines="rowHeight" />
<span v-else>{{ vModel }}</span> <span v-else>{{ vModel }}</span>

7
packages/nc-gui/components/cell/TimePicker.vue

@ -13,6 +13,8 @@ const emit = defineEmits(['update:modelValue'])
const { isMysql } = useProject() const { isMysql } = useProject()
const { showNull } = useGlobal()
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
const active = inject(ActiveCellInj, ref(false)) const active = inject(ActiveCellInj, ref(false))
@ -72,6 +74,8 @@ watch(
{ flush: 'post' }, { flush: 'post' },
) )
const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isTimeInvalid ? 'Invalid time' : ''))
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
case 'Enter': case 'Enter':
@ -97,7 +101,8 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
use12-hours use12-hours
format="HH:mm" format="HH:mm"
class="!w-full !px-0 !border-none" class="!w-full !px-0 !border-none"
:placeholder="isTimeInvalid ? 'Invalid time' : ''" :class="{ 'nc-null': modelValue === null && showNull }"
:placeholder="placeholder"
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true" :input-read-only="true"
:open="(readOnly || (localState && isPk)) && !active && !editable ? false : open" :open="(readOnly || (localState && isPk)) && !active && !editable ? false : open"

4
packages/nc-gui/components/cell/Url.vue

@ -24,6 +24,8 @@ const emit = defineEmits(['update:modelValue'])
const { t } = useI18n() const { t } = useI18n()
const { showNull } = useGlobal()
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
const editEnabled = inject(EditModeInj)! const editEnabled = inject(EditModeInj)!
@ -88,6 +90,8 @@ watch(
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<nuxt-link <nuxt-link
v-else-if="isValid && !cellUrlOptions?.overlay" v-else-if="isValid && !cellUrlOptions?.overlay"
no-prefetch no-prefetch

5
packages/nc-gui/components/cell/YearPicker.vue

@ -11,6 +11,8 @@ const { modelValue, isPk = false } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { showNull } = useGlobal()
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
const active = inject(ActiveCellInj, ref(false)) const active = inject(ActiveCellInj, ref(false))
@ -58,7 +60,7 @@ watch(
{ flush: 'post' }, { flush: 'post' },
) )
const placeholder = computed(() => (isYearInvalid ? 'Invalid year' : '')) const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isYearInvalid ? 'Invalid year' : ''))
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
@ -82,6 +84,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
picker="year" picker="year"
:bordered="false" :bordered="false"
class="!w-full !px-0 !border-none" class="!w-full !px-0 !border-none"
:class="{ 'nc-null': modelValue === null && showNull }"
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true" :input-read-only="true"

6
packages/nc-gui/components/dashboard/settings/Misc.vue

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useGlobal, useProject, watch } from '#imports' import { useGlobal, useProject, watch } from '#imports'
const { includeM2M } = useGlobal() const { includeM2M, showNull } = useGlobal()
const { loadTables } = useProject() const { loadTables } = useProject()
watch(includeM2M, async () => await loadTables()) watch(includeM2M, async () => await loadTables())
@ -16,6 +16,10 @@ watch(includeM2M, async () => await loadTables())
{{ $t('msg.info.showM2mTables') }} {{ $t('msg.info.showM2mTables') }}
</a-checkbox> </a-checkbox>
</div> </div>
<div class="flex flex-row items-center w-full mb-4 gap-2">
<!-- Show NULL -->
<a-checkbox v-model:checked="showNull" v-e="['c:settings:show-null']" class="nc-settings-show-null">Show NULL</a-checkbox>
</div>
</div> </div>
</div> </div>
</template> </template>

1
packages/nc-gui/composables/useGlobal/state.ts

@ -62,6 +62,7 @@ export function useGlobalState(storageKey = 'nocodb-gui-v2'): State {
filterAutoSave: true, filterAutoSave: true,
previewAs: null, previewAs: null,
includeM2M: false, includeM2M: false,
showNull: false,
currentVersion: null, currentVersion: null,
latestRelease: null, latestRelease: null,
hiddenRelease: null, hiddenRelease: null,

1
packages/nc-gui/composables/useGlobal/types.ts

@ -28,6 +28,7 @@ export interface StoredState {
filterAutoSave: boolean filterAutoSave: boolean
previewAs: ProjectRole | null previewAs: ProjectRole | null
includeM2M: boolean includeM2M: boolean
showNull: boolean
currentVersion: string | null currentVersion: string | null
latestRelease: string | null latestRelease: string | null
hiddenRelease: string | null hiddenRelease: string | null

2
tests/playwright/pages/Dashboard/Settings/Miscellaneous.ts

@ -14,7 +14,7 @@ export class MiscSettingsPage extends BasePage {
} }
async clickShowM2MTables() { async clickShowM2MTables() {
const clickAction = this.get().locator('input[type="checkbox"]').click(); const clickAction = this.get().locator('input[type="checkbox"]').first().click();
await this.waitForResponse({ await this.waitForResponse({
uiAction: clickAction, uiAction: clickAction,
requestUrlPathToMatch: 'tables?includeM2M', requestUrlPathToMatch: 'tables?includeM2M',

Loading…
Cancel
Save