Browse Source

WIP barcode column type

pull/4641/head
flisowna 2 years ago
parent
commit
1131b67363
  1. 1
      packages/nc-gui/components/smartsheet/Gallery.vue
  2. 10
      packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue
  3. 3
      packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts
  4. 61
      packages/nc-gui/components/virtual-cell/Barcode.vue
  5. 34
      packages/nc-gui/package-lock.json
  6. 3
      packages/nc-gui/package.json
  7. 2
      packages/nc-gui/plugins/ant.ts
  8. 2
      packages/nocodb-sdk/src/lib/columnRules/index.ts
  9. 34
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts
  10. 11
      packages/nocodb/src/lib/meta/api/columnApis.ts
  11. 4
      packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts
  12. 26
      packages/nocodb/src/lib/migrations/v2/nc_023_barcode_column_type.ts
  13. 1
      packages/nocodb/tests/unit/factory/column.ts

1
packages/nc-gui/components/smartsheet/Gallery.vue

@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ViewTypes, isVirtualCol } from 'nocodb-sdk' import { ViewTypes, isVirtualCol } from 'nocodb-sdk'
import VueBarcode from '@chenfengyuan/vue-barcode'
import { import {
ActiveViewInj, ActiveViewInj,
ChangePageInj, ChangePageInj,

10
packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue

@ -40,11 +40,11 @@ const columnsAllowedAsQrValue = computed<SelectProps['options']>(() => {
onMounted(() => { onMounted(() => {
// set default value // set default value
vModel.value.fk_qr_value_column_id = (column?.value?.colOptions as Record<string, any>)?.fk_qr_value_column_id || '' vModel.value.fk_barcode_value_column_id = (column?.value?.colOptions as Record<string, any>)?.fk_barcode_value_column_id || ''
}) })
setAdditionalValidations({ setAdditionalValidations({
fk_qr_value_column_id: [{ required: true, message: 'Required' }], fk_barcode_value_column_id: [{ required: true, message: 'Required' }],
}) })
</script> </script>
@ -53,11 +53,11 @@ setAdditionalValidations({
<a-col :span="24"> <a-col :span="24">
<a-form-item <a-form-item
class="flex w-1/2 pb-2 nc-qr-code-value-column-select" class="flex w-1/2 pb-2 nc-qr-code-value-column-select"
:label="$t('labels.qrCodeValueColumn')" :label="$t('labels.barcodeValueColumn')"
v-bind="validateInfos.fk_qr_value_column_id" v-bind="validateInfos.fk_barcode_value_column_id"
> >
<a-select <a-select
v-model:value="vModel.fk_qr_value_column_id" v-model:value="vModel.fk_barcode_value_column_id"
:options="columnsAllowedAsQrValue" :options="columnsAllowedAsQrValue"
placeholder="Select a column for the Barcode value" placeholder="Select a column for the Barcode value"
@click.stop @click.stop

3
packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts

@ -9,6 +9,7 @@ import BTIcon from '~icons/mdi/table-arrow-left'
import MMIcon from '~icons/mdi/table-network' import MMIcon from '~icons/mdi/table-network'
import FormulaIcon from '~icons/mdi/math-integral' import FormulaIcon from '~icons/mdi/math-integral'
import QrCodeScan from '~icons/mdi/qrcode-scan' import QrCodeScan from '~icons/mdi/qrcode-scan'
import BarcodeScan from '~icons/mdi/barcode-scan'
import RollupIcon from '~icons/mdi/movie-roll' import RollupIcon from '~icons/mdi/movie-roll'
import CountIcon from '~icons/mdi/counter' import CountIcon from '~icons/mdi/counter'
import SpecificDBTypeIcon from '~icons/mdi/database-settings' import SpecificDBTypeIcon from '~icons/mdi/database-settings'
@ -32,6 +33,8 @@ const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => {
return { icon: FormulaIcon, color: 'text-grey' } return { icon: FormulaIcon, color: 'text-grey' }
case UITypes.QrCode: case UITypes.QrCode:
return { icon: QrCodeScan, color: 'text-grey' } return { icon: QrCodeScan, color: 'text-grey' }
case UITypes.Barcode:
return { icon: BarcodeScan, color: 'text-grey' }
case UITypes.Lookup: case UITypes.Lookup:
switch ((relationColumn?.colOptions as LinkToAnotherRecordType)?.type) { switch ((relationColumn?.colOptions as LinkToAnotherRecordType)?.type) {
case RelationTypes.MANY_TO_MANY: case RelationTypes.MANY_TO_MANY:

61
packages/nc-gui/components/virtual-cell/Barcode.vue

@ -0,0 +1,61 @@
<script setup lang="ts">
import VueBarcode from '@chenfengyuan/vue-barcode'
const maxNumberOfAllowedCharsForQrValue = 2000
const cellValue = inject(CellValueInj)
const barcodeValue = computed(() => String(cellValue?.value))
const tooManyCharsForQrCode = computed(() => barcodeValue?.value.length > maxNumberOfAllowedCharsForQrValue)
// const barcode = VueBarcode(barcodeValue, {
// width: 150,
// })
// const barcodeLarge = VueBarcode(barcodeValue, {
// width: 600,
// })
const modalVisible = ref(false)
const showQrModal = (ev: MouseEvent) => {
ev.stopPropagation()
modalVisible.value = true
}
const handleModalOkClick = () => (modalVisible.value = false)
const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning } = useShowNotEditableWarning()
</script>
<template>
<vue-barcode value="barcodeValue"></vue-barcode>
<a-modal
v-model:visible="modalVisible"
:class="{ active: modalVisible }"
wrap-class-name="nc-qr-code-large"
:body-style="{ padding: '0px' }"
@ok="handleModalOkClick"
>
<template #footer>
<div class="mr-4" data-testid="nc-qr-code-large-value-label">heja</div>
</template>
<!-- <img v-if="barcodeValue && !tooManyCharsForQrCode" :src="qrCodeLarge" alt="QR Code" /> -->
</a-modal>
<div @click="showQrModal">
<VueBarcode value="barcodeValue"></VueBarcode>
</div>
<!-- <div v-if="tooManyCharsForQrCode" class="text-left text-wrap mt-2 text-[#e65100] text-xs">
{{ $t('labels.qrCodeValueTooLong') }}
</div>
<img v-if="qrValue && !tooManyCharsForQrCode" :src="qrCode" alt="QR Code" @click="showQrModal" /> -->
<!-- <div v-if="showEditNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs">
{{ $t('msg.warning.nonEditableFields.computedFieldUnableToClear') }}
</div>
<div v-if="showClearNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs">
{{ $t('msg.warning.nonEditableFields.qrFieldsCannotBeDirectlyChanged') }}
</div>
-->
</template>

34
packages/nc-gui/package-lock.json generated

@ -8,6 +8,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@chenfengyuan/vue-barcode": "^2.0.1",
"@ckpack/vue-color": "^1.2.0", "@ckpack/vue-color": "^1.2.0",
"@vue-flow/additional-components": "^1.2.0", "@vue-flow/additional-components": "^1.2.0",
"@vue-flow/core": "^1.3.0", "@vue-flow/core": "^1.3.0",
@ -21,6 +22,7 @@
"dayjs": "^1.11.3", "dayjs": "^1.11.3",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"httpsnippet": "^2.0.0", "httpsnippet": "^2.0.0",
"jsbarcode": "^3.11.5",
"jsep": "^1.3.6", "jsep": "^1.3.6",
"just-clone": "^6.1.1", "just-clone": "^6.1.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
@ -34,7 +36,6 @@
"tinycolor2": "^1.4.2", "tinycolor2": "^1.4.2",
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",
"validator": "^13.7.0", "validator": "^13.7.0",
"vue-barcode": "^1.3.0",
"vue-dompurify-html": "^3.0.0", "vue-dompurify-html": "^3.0.0",
"vue-github-button": "^3.0.3", "vue-github-button": "^3.0.3",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
@ -869,6 +870,15 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@chenfengyuan/vue-barcode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@chenfengyuan/vue-barcode/-/vue-barcode-2.0.1.tgz",
"integrity": "sha512-YTSV1o0vOIRDZMiZZ9t09zkc9VZXN8CG8rriJUZUMtrvdg4Xt/qOOuzRlZEyRD7hsSYjbQy/3RcByGn1WOb1EA==",
"peerDependencies": {
"jsbarcode": "^3.11.0",
"vue": "^3.0.0"
}
},
"node_modules/@ckpack/vue-color": { "node_modules/@ckpack/vue-color": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ckpack/vue-color/-/vue-color-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@ckpack/vue-color/-/vue-color-1.2.0.tgz",
@ -16866,14 +16876,6 @@
"@vue/shared": "3.2.41" "@vue/shared": "3.2.41"
} }
}, },
"node_modules/vue-barcode": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/vue-barcode/-/vue-barcode-1.3.0.tgz",
"integrity": "sha512-DxQ0hxes/dP6GajsJumpW6jV14VwlnTwStZbtE6G0wkewuJVDoDOdxUr5seGuxsMT9fJ0aty4X47Z5TG0M/gxg==",
"dependencies": {
"jsbarcode": "^3.5.8"
}
},
"node_modules/vue-bundle-renderer": { "node_modules/vue-bundle-renderer": {
"version": "0.4.4", "version": "0.4.4",
"resolved": "https://registry.npmjs.org/vue-bundle-renderer/-/vue-bundle-renderer-0.4.4.tgz", "resolved": "https://registry.npmjs.org/vue-bundle-renderer/-/vue-bundle-renderer-0.4.4.tgz",
@ -18229,6 +18231,12 @@
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
} }
}, },
"@chenfengyuan/vue-barcode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@chenfengyuan/vue-barcode/-/vue-barcode-2.0.1.tgz",
"integrity": "sha512-YTSV1o0vOIRDZMiZZ9t09zkc9VZXN8CG8rriJUZUMtrvdg4Xt/qOOuzRlZEyRD7hsSYjbQy/3RcByGn1WOb1EA==",
"requires": {}
},
"@ckpack/vue-color": { "@ckpack/vue-color": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ckpack/vue-color/-/vue-color-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@ckpack/vue-color/-/vue-color-1.2.0.tgz",
@ -29744,14 +29752,6 @@
"@vue/shared": "3.2.41" "@vue/shared": "3.2.41"
} }
}, },
"vue-barcode": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/vue-barcode/-/vue-barcode-1.3.0.tgz",
"integrity": "sha512-DxQ0hxes/dP6GajsJumpW6jV14VwlnTwStZbtE6G0wkewuJVDoDOdxUr5seGuxsMT9fJ0aty4X47Z5TG0M/gxg==",
"requires": {
"jsbarcode": "^3.5.8"
}
},
"vue-bundle-renderer": { "vue-bundle-renderer": {
"version": "0.4.4", "version": "0.4.4",
"resolved": "https://registry.npmjs.org/vue-bundle-renderer/-/vue-bundle-renderer-0.4.4.tgz", "resolved": "https://registry.npmjs.org/vue-bundle-renderer/-/vue-bundle-renderer-0.4.4.tgz",

3
packages/nc-gui/package.json

@ -31,6 +31,7 @@
"ci:run": "export NODE_OPTIONS=\"--max_old_space_size=16384\"; npm install; NUXT_PAGE_TRANSITION_DISABLE=true npm run build; NUXT_PUBLIC_NC_BACKEND_URL=http://localhost:8080 npm run start &" "ci:run": "export NODE_OPTIONS=\"--max_old_space_size=16384\"; npm install; NUXT_PAGE_TRANSITION_DISABLE=true npm run build; NUXT_PUBLIC_NC_BACKEND_URL=http://localhost:8080 npm run start &"
}, },
"dependencies": { "dependencies": {
"@chenfengyuan/vue-barcode": "^2.0.1",
"@ckpack/vue-color": "^1.2.0", "@ckpack/vue-color": "^1.2.0",
"@vue-flow/additional-components": "^1.2.0", "@vue-flow/additional-components": "^1.2.0",
"@vue-flow/core": "^1.3.0", "@vue-flow/core": "^1.3.0",
@ -44,6 +45,7 @@
"dayjs": "^1.11.3", "dayjs": "^1.11.3",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"httpsnippet": "^2.0.0", "httpsnippet": "^2.0.0",
"jsbarcode": "^3.11.5",
"jsep": "^1.3.6", "jsep": "^1.3.6",
"just-clone": "^6.1.1", "just-clone": "^6.1.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
@ -57,7 +59,6 @@
"tinycolor2": "^1.4.2", "tinycolor2": "^1.4.2",
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",
"validator": "^13.7.0", "validator": "^13.7.0",
"vue-barcode": "^1.3.0",
"vue-dompurify-html": "^3.0.0", "vue-dompurify-html": "^3.0.0",
"vue-github-button": "^3.0.3", "vue-github-button": "^3.0.3",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",

2
packages/nc-gui/plugins/ant.ts

@ -1,7 +1,9 @@
import { Menu as AntMenu, Modal as AntModal, message } from 'ant-design-vue' import { Menu as AntMenu, Modal as AntModal, message } from 'ant-design-vue'
import VueBarcode from '@chenfengyuan/vue-barcode'
import { defineNuxtPlugin } from '#imports' import { defineNuxtPlugin } from '#imports'
export default defineNuxtPlugin((nuxtApp) => { export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component(VueBarcode.name, VueBarcode)
nuxtApp.vueApp.component(AntMenu.name, AntMenu) nuxtApp.vueApp.component(AntMenu.name, AntMenu)
nuxtApp.vueApp.component(AntModal.name, AntModal) nuxtApp.vueApp.component(AntModal.name, AntModal)
message.config({ message.config({

2
packages/nocodb-sdk/src/lib/columnRules/index.ts

@ -1 +1 @@
export * from './QrCodeRules'; export * from './QrAndBarcodeRules';

34
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts

@ -45,6 +45,7 @@ import { customAlphabet } from 'nanoid';
import DOMPurify from 'isomorphic-dompurify'; import DOMPurify from 'isomorphic-dompurify';
import { sanitize, unsanitize } from './helpers/sanitize'; import { sanitize, unsanitize } from './helpers/sanitize';
import QrCodeColumn from '../../../../models/QrCodeColumn'; import QrCodeColumn from '../../../../models/QrCodeColumn';
import BarcodeColumn from '../../../../models/BarcodeColumn';
const GROUP_COL = '__nc_group_id'; const GROUP_COL = '__nc_group_id';
@ -1470,6 +1471,38 @@ class BaseModelSqlv2 {
break; break;
} }
case 'Barcode': {
const barcodeColumn = await column.getColOptions<BarcodeColumn>();
const barcodeValueColumn = await Column.get({
colId: barcodeColumn.fk_barcode_value_column_id,
});
// If the referenced value cannot be found: cancel current iteration
if (barcodeValueColumn == null) {
break;
}
switch (barcodeValueColumn.uidt) {
case UITypes.Formula:
try {
const selectQb = await this.getSelectQueryBuilderForFormula(
barcodeValueColumn
);
qb.select({
[column.column_name]: selectQb.builder,
});
} catch {
continue;
}
break;
default: {
qb.select({ [column.column_name]: barcodeValueColumn.column_name });
break;
}
}
break;
}
case 'Formula': case 'Formula':
{ {
try { try {
@ -2217,6 +2250,7 @@ class BaseModelSqlv2 {
f.uidt !== UITypes.Lookup && f.uidt !== UITypes.Lookup &&
f.uidt !== UITypes.Formula && f.uidt !== UITypes.Formula &&
f.uidt !== UITypes.QrCode && f.uidt !== UITypes.QrCode &&
f.uidt !== UITypes.Barcode &&
f.uidt !== UITypes.SpecificDBType f.uidt !== UITypes.SpecificDBType
) )
.sort( .sort(

11
packages/nocodb/src/lib/meta/api/columnApis.ts

@ -512,6 +512,12 @@ export async function columnAdd(
fk_model_id: table.id, fk_model_id: table.id,
}); });
break; break;
case UITypes.Barcode:
await Column.insert({
...colBody,
fk_model_id: table.id,
});
break;
case UITypes.Formula: case UITypes.Formula:
colBody.formula = await substituteColumnAliasWithIdInFormula( colBody.formula = await substituteColumnAliasWithIdInFormula(
colBody.formula_raw || colBody.formula, colBody.formula_raw || colBody.formula,
@ -738,11 +744,12 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
UITypes.LinkToAnotherRecord, UITypes.LinkToAnotherRecord,
UITypes.Formula, UITypes.Formula,
UITypes.QrCode, UITypes.QrCode,
UITypes.Barcode,
UITypes.ForeignKey, UITypes.ForeignKey,
].includes(column.uidt) ].includes(column.uidt)
) { ) {
if (column.uidt === colBody.uidt) { if (column.uidt === colBody.uidt) {
if (column.uidt === UITypes.QrCode) { if ([UITypes.QrCode, UITypes.Barcode].includes(column.uidt)) {
await Column.update(column.id, { await Column.update(column.id, {
...column, ...column,
...colBody, ...colBody,
@ -774,6 +781,7 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
UITypes.LinkToAnotherRecord, UITypes.LinkToAnotherRecord,
UITypes.Formula, UITypes.Formula,
UITypes.QrCode, UITypes.QrCode,
UITypes.Barcode,
UITypes.ForeignKey, UITypes.ForeignKey,
].includes(colBody.uidt) ].includes(colBody.uidt)
) { ) {
@ -1460,6 +1468,7 @@ export async function columnDelete(req: Request, res: Response<TableType>) {
case UITypes.Lookup: case UITypes.Lookup:
case UITypes.Rollup: case UITypes.Rollup:
case UITypes.QrCode: case UITypes.QrCode:
case UITypes.Barcode:
case UITypes.Formula: case UITypes.Formula:
await Column.delete(req.params.columnId); await Column.delete(req.params.columnId);
break; break;

4
packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts

@ -10,6 +10,7 @@ import * as nc_019_add_meta_in_meta_tables from './v2/nc_019_add_meta_in_meta_ta
import * as nc_020_kanban_view from './v2/nc_020_kanban_view'; import * as nc_020_kanban_view from './v2/nc_020_kanban_view';
import * as nc_021_add_fields_in_token from './v2/nc_021_add_fields_in_token'; import * as nc_021_add_fields_in_token from './v2/nc_021_add_fields_in_token';
import * as nc_022_qr_code_column_type from './v2/nc_022_qr_code_column_type'; import * as nc_022_qr_code_column_type from './v2/nc_022_qr_code_column_type';
import * as nc_023_barcode_column_type from './v2/nc_023_barcode_column_type';
// Create a custom migration source class // Create a custom migration source class
export default class XcMigrationSourcev2 { export default class XcMigrationSourcev2 {
@ -31,6 +32,7 @@ export default class XcMigrationSourcev2 {
'nc_020_kanban_view', 'nc_020_kanban_view',
'nc_021_add_fields_in_token', 'nc_021_add_fields_in_token',
'nc_022_qr_code_column_type', 'nc_022_qr_code_column_type',
'nc_023_barcode_column_type',
]); ]);
} }
@ -64,6 +66,8 @@ export default class XcMigrationSourcev2 {
return nc_021_add_fields_in_token; return nc_021_add_fields_in_token;
case 'nc_022_qr_code_column_type': case 'nc_022_qr_code_column_type':
return nc_022_qr_code_column_type; return nc_022_qr_code_column_type;
case 'nc_023_barcode_column_type':
return nc_023_barcode_column_type;
} }
} }
} }

26
packages/nocodb/src/lib/migrations/v2/nc_023_barcode_column_type.ts

@ -0,0 +1,26 @@
import { MetaTable } from '../../utils/globals';
import { Knex } from 'knex';
const up = async (knex: Knex) => {
await knex.schema.createTable(MetaTable.COL_BARCODE, (table) => {
table.string('id', 20).primary().notNullable();
table.string('fk_column_id', 20);
table.foreign('fk_column_id').references(`${MetaTable.COLUMNS}.id`);
table.string('fk_barcode_value_column_id', 20);
table
.foreign('fk_barcode_value_column_id')
.references(`${MetaTable.COLUMNS}.id`);
table.boolean('deleted');
table.float('order');
table.timestamps(true, true);
});
};
const down = async (knex: Knex) => {
await knex.schema.dropTable(MetaTable.COL_BARCODE);
};
export { up, down };

1
packages/nocodb/tests/unit/factory/column.ts

@ -262,6 +262,7 @@ export {
defaultColumns, defaultColumns,
createColumn, createColumn,
createQrCodeColumn, createQrCodeColumn,
createBarcodeColumn,
createRollupColumn, createRollupColumn,
createLookupColumn, createLookupColumn,
createLtarColumn, createLtarColumn,

Loading…
Cancel
Save