Browse Source

fix: various things

Signed-off-by: mertmit <mertmit99@gmail.com>
pull/6513/head
mertmit 1 year ago
parent
commit
bd76d3e32d
  1. 196
      packages/nc-gui/assets/img/fieldPlaceholder.svg
  2. 9
      packages/nc-gui/components/cell/Json.vue
  3. 1
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  4. 6
      packages/nc-gui/components/dashboard/settings/Modal.vue
  5. 4
      packages/nc-gui/components/monaco/Editor.vue
  6. 3
      packages/nc-gui/components/smartsheet/Details.vue
  7. 852
      packages/nc-gui/components/smartsheet/details/Fields.vue
  8. 5
      packages/nc-gui/components/smartsheet/grid/Table.vue
  9. 4
      packages/nc-gui/components/smartsheet/grid/newFile.ts
  10. 2
      packages/nc-gui/components/smartsheet/topbar/SelectMode.vue
  11. 4
      packages/nc-gui/composables/useApi/interceptors.ts
  12. 4
      packages/nc-gui/composables/useViewFilters.ts
  13. 1
      packages/nc-gui/context/index.ts
  14. 2
      packages/nc-gui/pages/index/[typeOrId]/view/[viewId].vue
  15. 18
      packages/nc-gui/store/views.ts
  16. 19
      packages/nocodb/src/db/conditionV2.ts
  17. 80
      packages/nocodb/src/services/datas.service.ts
  18. 133
      packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts
  19. 2
      tests/playwright/pages/Dashboard/Grid/index.ts
  20. 5
      tests/playwright/pages/Dashboard/common/Topbar/Share.ts

196
packages/nc-gui/assets/img/fieldPlaceholder.svg

@ -0,0 +1,196 @@
<svg width="166" height="80" viewBox="0 0 166 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_784_33028" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="166" height="80">
<path d="M163.451 0.283691H2.54954C1.29794 0.283691 0.283325 1.29978 0.283325 2.55319V77.4468C0.283325 78.7002 1.29794 79.7163 2.54954 79.7163H163.451C164.702 79.7163 165.717 78.7002 165.717 77.4468V2.55319C165.717 1.29978 164.702 0.283691 163.451 0.283691Z" fill="white"/>
</mask>
<g mask="url(#mask0_784_33028)">
<path d="M163.451 0.283691H2.54954C1.29794 0.283691 0.283325 1.29978 0.283325 2.55319V77.4468C0.283325 78.7002 1.29794 79.7163 2.54954 79.7163H163.451C164.702 79.7163 165.717 78.7002 165.717 77.4468V2.55319C165.717 1.29978 164.702 0.283691 163.451 0.283691Z" fill="white"/>
<g filter="url(#filter0_dd_784_33028)">
<path d="M7.08193 3.68799H4.81572C4.18993 3.68799 3.68262 4.19603 3.68262 4.82274V7.09224C3.68262 7.71895 4.18993 8.227 4.81572 8.227H7.08193C7.70773 8.227 8.21504 7.71895 8.21504 7.09224V4.82274C8.21504 4.19603 7.70773 3.68799 7.08193 3.68799Z" fill="#3366FF"/>
<path d="M7.0819 3.82983H4.81569C4.26811 3.82983 3.82422 4.27437 3.82422 4.82274V7.09225C3.82422 7.64061 4.26811 8.08515 4.81569 8.08515H7.0819C7.62947 8.08515 8.07337 7.64061 8.07337 7.09225V4.82274C8.07337 4.27437 7.62947 3.82983 7.0819 3.82983Z" stroke="#3366FF"/>
</g>
<path d="M7.08201 5.10645L5.52399 6.66673L4.8158 5.95751" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.8806 3.68799H11.6144C10.9886 3.68799 10.4813 4.19603 10.4813 4.82274V7.09224C10.4813 7.71895 10.9886 8.227 11.6144 8.227H13.8806C14.5064 8.227 15.0137 7.71895 15.0137 7.09224V4.82274C15.0137 4.19603 14.5064 3.68799 13.8806 3.68799Z" fill="#E7E7E9"/>
<path d="M50.14 3.89429H18.413C17.7872 3.89429 17.2799 4.40233 17.2799 5.02904V6.88592C17.2799 7.51262 17.7872 8.02067 18.413 8.02067H50.14C50.7658 8.02067 51.2731 7.51262 51.2731 6.88592V5.02904C51.2731 4.40233 50.7658 3.89429 50.14 3.89429Z" fill="#E7E7E9"/>
<path d="M165.717 11.3475H0.283325V11.9149H165.717V11.3475Z" fill="#E7E7E9"/>
<g filter="url(#filter1_dd_784_33028)">
<path d="M7.08193 15.0355H4.81572C4.18993 15.0355 3.68262 15.5436 3.68262 16.1703V18.4398C3.68262 19.0665 4.18993 19.5745 4.81572 19.5745H7.08193C7.70773 19.5745 8.21504 19.0665 8.21504 18.4398V16.1703C8.21504 15.5436 7.70773 15.0355 7.08193 15.0355Z" fill="#3366FF"/>
<path d="M7.0819 15.1774H4.81569C4.26811 15.1774 3.82422 15.6219 3.82422 16.1703V18.4398C3.82422 18.9881 4.26811 19.4327 4.81569 19.4327H7.0819C7.62947 19.4327 8.07337 18.9881 8.07337 18.4398V16.1703C8.07337 15.6219 7.62947 15.1774 7.0819 15.1774Z" stroke="#3366FF"/>
</g>
<path d="M7.08201 16.4539L5.52399 18.0141L4.8158 17.3049" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.8806 15.0355H11.6144C10.9886 15.0355 10.4813 15.5436 10.4813 16.1703V18.4398C10.4813 19.0665 10.9886 19.5745 11.6144 19.5745H13.8806C14.5064 19.5745 15.0137 19.0665 15.0137 18.4398V16.1703C15.0137 15.5436 14.5064 15.0355 13.8806 15.0355Z" fill="#E7E7E9"/>
<path d="M75.6349 15.0355H18.413C17.7872 15.0355 17.2799 15.5436 17.2799 16.1703V18.4398C17.2799 19.0665 17.7872 19.5745 18.413 19.5745H75.6349C76.2607 19.5745 76.768 19.0665 76.768 18.4398V16.1703C76.768 15.5436 76.2607 15.0355 75.6349 15.0355Z" fill="#E7E7E9"/>
<path d="M165.717 22.6951H0.283325V23.2624H165.717V22.6951Z" fill="#E7E7E9"/>
<path d="M165.575 23.1206H0.424927V34.1844H165.575V23.1206Z" fill="#EBF0FF"/>
<g filter="url(#filter2_dd_784_33028)">
<path d="M7.08193 26.3829H4.81572C4.18993 26.3829 3.68262 26.891 3.68262 27.5177V29.7872C3.68262 30.4139 4.18993 30.9219 4.81572 30.9219H7.08193C7.70773 30.9219 8.21504 30.4139 8.21504 29.7872V27.5177C8.21504 26.891 7.70773 26.3829 7.08193 26.3829Z" fill="#3366FF"/>
<path d="M7.0819 26.5248H4.81569C4.26811 26.5248 3.82422 26.9693 3.82422 27.5177V29.7872C3.82422 30.3356 4.26811 30.7801 4.81569 30.7801H7.0819C7.62947 30.7801 8.07337 30.3356 8.07337 29.7872V27.5177C8.07337 26.9693 7.62947 26.5248 7.0819 26.5248Z" stroke="#3366FF"/>
</g>
<path d="M7.08201 27.8014L5.52399 29.3617L4.8158 28.6525" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.8806 26.3829H11.6144C10.9886 26.3829 10.4813 26.891 10.4813 27.5177V29.7872C10.4813 30.4139 10.9886 30.9219 11.6144 30.9219H13.8806C14.5064 30.9219 15.0137 30.4139 15.0137 29.7872V27.5177C15.0137 26.891 14.5064 26.3829 13.8806 26.3829Z" fill="#E7E7E9"/>
<path d="M50.14 26.5894H18.413C17.7872 26.5894 17.2799 27.0974 17.2799 27.7241V29.581C17.2799 30.2077 17.7872 30.7157 18.413 30.7157H50.14C50.7658 30.7157 51.2731 30.2077 51.2731 29.581V27.7241C51.2731 27.0974 50.7658 26.5894 50.14 26.5894Z" fill="#E7E7E9"/>
<path d="M165 23H1V34H165V23Z" stroke="#3366FF"/>
<g filter="url(#filter3_dd_784_33028)">
<path d="M7.08193 37.7305H4.81572C4.18993 37.7305 3.68262 38.2385 3.68262 38.8652V41.1347C3.68262 41.7614 4.18993 42.2695 4.81572 42.2695H7.08193C7.70773 42.2695 8.21504 41.7614 8.21504 41.1347V38.8652C8.21504 38.2385 7.70773 37.7305 7.08193 37.7305Z" fill="#3366FF"/>
<path d="M7.0819 37.8723H4.81569C4.26811 37.8723 3.82422 38.3169 3.82422 38.8652V41.1347C3.82422 41.6831 4.26811 42.1276 4.81569 42.1276H7.0819C7.62947 42.1276 8.07337 41.6831 8.07337 41.1347V38.8652C8.07337 38.3169 7.62947 37.8723 7.0819 37.8723Z" stroke="#3366FF"/>
</g>
<path d="M7.08201 39.1489L5.52399 40.7092L4.8158 40" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.8806 37.7305H11.6144C10.9886 37.7305 10.4813 38.2385 10.4813 38.8652V41.1347C10.4813 41.7614 10.9886 42.2695 11.6144 42.2695H13.8806C14.5064 42.2695 15.0137 41.7614 15.0137 41.1347V38.8652C15.0137 38.2385 14.5064 37.7305 13.8806 37.7305Z" fill="#E7E7E9"/>
<path d="M75.6349 37.7305H18.413C17.7872 37.7305 17.2799 38.2385 17.2799 38.8652V41.1347C17.2799 41.7614 17.7872 42.2695 18.413 42.2695H75.6349C76.2607 42.2695 76.768 41.7614 76.768 41.1347V38.8652C76.768 38.2385 76.2607 37.7305 75.6349 37.7305Z" fill="#E7E7E9"/>
<path d="M165.717 45.3901H0.283325V45.9575H165.717V45.3901Z" fill="#E7E7E9"/>
<g filter="url(#filter4_dd_784_33028)">
<path d="M7.08193 49.078H4.81572C4.18993 49.078 3.68262 49.586 3.68262 50.2128V52.4823C3.68262 53.109 4.18993 53.617 4.81572 53.617H7.08193C7.70773 53.617 8.21504 53.109 8.21504 52.4823V50.2128C8.21504 49.586 7.70773 49.078 7.08193 49.078Z" fill="#3366FF"/>
<path d="M7.0819 49.2198H4.81569C4.26811 49.2198 3.82422 49.6644 3.82422 50.2128V52.4823C3.82422 53.0306 4.26811 53.4752 4.81569 53.4752H7.0819C7.62947 53.4752 8.07337 53.0306 8.07337 52.4823V50.2128C8.07337 49.6644 7.62947 49.2198 7.0819 49.2198Z" stroke="#3366FF"/>
</g>
<path d="M7.08201 50.4965L5.52399 52.0567L4.8158 51.3475" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.8806 49.078H11.6144C10.9886 49.078 10.4813 49.586 10.4813 50.2128V52.4823C10.4813 53.109 10.9886 53.617 11.6144 53.617H13.8806C14.5064 53.617 15.0137 53.109 15.0137 52.4823V50.2128C15.0137 49.586 14.5064 49.078 13.8806 49.078Z" fill="#E7E7E9"/>
<path d="M50.14 49.2843H18.413C17.7872 49.2843 17.2799 49.7923 17.2799 50.4191V52.2759C17.2799 52.9026 17.7872 53.4107 18.413 53.4107H50.14C50.7658 53.4107 51.2731 52.9026 51.2731 52.2759V50.4191C51.2731 49.7923 50.7658 49.2843 50.14 49.2843Z" fill="#E7E7E9"/>
<path d="M165.717 56.7375H0.283325V57.3049H165.717V56.7375Z" fill="#E7E7E9"/>
<g filter="url(#filter5_dd_784_33028)">
<path d="M7.08193 60.4255H4.81572C4.18993 60.4255 3.68262 60.9336 3.68262 61.5603V63.8298C3.68262 64.4565 4.18993 64.9645 4.81572 64.9645H7.08193C7.70773 64.9645 8.21504 64.4565 8.21504 63.8298V61.5603C8.21504 60.9336 7.70773 60.4255 7.08193 60.4255Z" fill="#3366FF"/>
<path d="M7.0819 60.5674H4.81569C4.26811 60.5674 3.82422 61.0119 3.82422 61.5603V63.8298C3.82422 64.3782 4.26811 64.8227 4.81569 64.8227H7.0819C7.62947 64.8227 8.07337 64.3782 8.07337 63.8298V61.5603C8.07337 61.0119 7.62947 60.5674 7.0819 60.5674Z" stroke="#3366FF"/>
</g>
<path d="M7.08201 61.844L5.52399 63.4043L4.8158 62.6951" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.8806 60.4255H11.6144C10.9886 60.4255 10.4813 60.9336 10.4813 61.5603V63.8298C10.4813 64.4565 10.9886 64.9645 11.6144 64.9645H13.8806C14.5064 64.9645 15.0137 64.4565 15.0137 63.8298V61.5603C15.0137 60.9336 14.5064 60.4255 13.8806 60.4255Z" fill="#E7E7E9"/>
<path d="M75.6349 60.4255H18.413C17.7872 60.4255 17.2799 60.9336 17.2799 61.5603V63.8298C17.2799 64.4565 17.7872 64.9645 18.413 64.9645H75.6349C76.2607 64.9645 76.768 64.4565 76.768 63.8298V61.5603C76.768 60.9336 76.2607 60.4255 75.6349 60.4255Z" fill="#E7E7E9"/>
<path d="M165.717 68.0851H0.283325V68.6525H165.717V68.0851Z" fill="#E7E7E9"/>
<g filter="url(#filter6_dd_784_33028)">
<path d="M7.08193 71.7731H4.81572C4.18993 71.7731 3.68262 72.2811 3.68262 72.9078V75.1773C3.68262 75.804 4.18993 76.3121 4.81572 76.3121H7.08193C7.70773 76.3121 8.21504 75.804 8.21504 75.1773V72.9078C8.21504 72.2811 7.70773 71.7731 7.08193 71.7731Z" fill="#3366FF"/>
<path d="M7.0819 71.9149H4.81569C4.26811 71.9149 3.82422 72.3595 3.82422 72.9078V75.1773C3.82422 75.7257 4.26811 76.1702 4.81569 76.1702H7.0819C7.62947 76.1702 8.07337 75.7257 8.07337 75.1773V72.9078C8.07337 72.3595 7.62947 71.9149 7.0819 71.9149Z" stroke="#3366FF"/>
</g>
<path d="M7.08201 73.1915L5.52399 74.7518L4.8158 74.0426" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.8806 71.7731H11.6144C10.9886 71.7731 10.4813 72.2811 10.4813 72.9078V75.1773C10.4813 75.804 10.9886 76.3121 11.6144 76.3121H13.8806C14.5064 76.3121 15.0137 75.804 15.0137 75.1773V72.9078C15.0137 72.2811 14.5064 71.7731 13.8806 71.7731Z" fill="#E7E7E9"/>
<path d="M50.14 71.9792H18.413C17.7872 71.9792 17.2799 72.4873 17.2799 73.114V74.9709C17.2799 75.5976 17.7872 76.1056 18.413 76.1056H50.14C50.7658 76.1056 51.2731 75.5976 51.2731 74.9709V73.114C51.2731 72.4873 50.7658 71.9792 50.14 71.9792Z" fill="#E7E7E9"/>
<path d="M165.717 79.4326H0.283325V80H165.717V79.4326Z" fill="#E7E7E9"/>
</g>
<g filter="url(#filter7_d_784_33028)">
<path d="M115.669 34.6749C115.494 34.4499 115.275 33.9937 114.894 33.4249C114.675 33.1124 114.138 32.5187 113.975 32.2124C113.858 32.0264 113.824 31.7997 113.881 31.5874C113.979 31.1838 114.362 30.9161 114.775 30.9624C115.094 31.0266 115.388 31.1831 115.619 31.4124C115.78 31.5644 115.929 31.7296 116.063 31.9062C116.163 32.0312 116.188 32.0812 116.3 32.2249C116.413 32.3687 116.488 32.5124 116.431 32.2999C116.388 31.9874 116.313 31.4624 116.206 30.9937C116.125 30.6374 116.106 30.5812 116.031 30.3124C115.956 30.0437 115.913 29.8187 115.831 29.5124C115.757 29.2116 115.699 28.907 115.656 28.5999C115.577 28.2073 115.635 27.7995 115.819 27.4437C116.037 27.2383 116.357 27.1841 116.631 27.3062C116.907 27.5095 117.112 27.7935 117.219 28.1187C117.383 28.5189 117.492 28.9394 117.544 29.3687C117.644 29.9937 117.838 30.9062 117.844 31.0937C117.844 30.8624 117.8 30.3749 117.844 30.1562C117.887 29.9282 118.046 29.7389 118.263 29.6562C118.449 29.5991 118.646 29.5862 118.838 29.6187C119.031 29.6592 119.203 29.7707 119.319 29.9312C119.464 30.2958 119.544 30.6828 119.556 31.0749C119.573 30.7316 119.632 30.3916 119.731 30.0624C119.836 29.9153 119.988 29.8092 120.163 29.7624C120.369 29.7247 120.581 29.7247 120.788 29.7624C120.957 29.8191 121.105 29.9259 121.213 30.0687C121.345 30.4002 121.425 30.7502 121.45 31.1062C121.45 31.1937 121.494 30.8624 121.631 30.6437C121.703 30.4316 121.882 30.2737 122.101 30.2295C122.321 30.1853 122.547 30.2616 122.695 30.4295C122.843 30.5974 122.89 30.8316 122.819 31.0437C122.819 31.4499 122.819 31.4312 122.819 31.7062C122.819 31.9812 122.819 32.2249 122.819 32.4562C122.796 32.8219 122.746 33.1854 122.669 33.5437C122.56 33.8606 122.409 34.1613 122.219 34.4374C121.915 34.7749 121.665 35.1563 121.475 35.5687C121.428 35.7736 121.407 35.9836 121.413 36.1937C121.412 36.3879 121.437 36.5812 121.488 36.7687C121.232 36.7957 120.974 36.7957 120.719 36.7687C120.475 36.7312 120.175 36.2437 120.094 36.0937C120.054 36.0132 119.971 35.9622 119.881 35.9622C119.791 35.9622 119.709 36.0132 119.669 36.0937C119.531 36.3312 119.225 36.7624 119.013 36.7874C118.594 36.8374 117.731 36.7874 117.05 36.7874C117.05 36.7874 117.163 36.1624 116.906 35.9374C116.65 35.7124 116.388 35.4499 116.194 35.2749L115.669 34.6749Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M115.669 34.6749C115.494 34.4499 115.275 33.9937 114.894 33.4249C114.675 33.1124 114.138 32.5187 113.975 32.2124C113.858 32.0264 113.824 31.7997 113.881 31.5874C113.979 31.1838 114.362 30.9161 114.775 30.9624C115.094 31.0266 115.388 31.1831 115.619 31.4124C115.78 31.5644 115.929 31.7296 116.063 31.9062C116.163 32.0312 116.188 32.0812 116.3 32.2249C116.413 32.3687 116.488 32.5124 116.431 32.2999C116.388 31.9874 116.313 31.4624 116.206 30.9937C116.125 30.6374 116.106 30.5812 116.031 30.3124C115.956 30.0437 115.913 29.8187 115.831 29.5124C115.757 29.2116 115.699 28.907 115.656 28.5999C115.577 28.2073 115.635 27.7995 115.819 27.4437C116.037 27.2383 116.357 27.1841 116.631 27.3062C116.907 27.5095 117.112 27.7935 117.219 28.1187C117.383 28.5189 117.492 28.9394 117.544 29.3687C117.644 29.9937 117.838 30.9062 117.844 31.0937C117.844 30.8624 117.8 30.3749 117.844 30.1562C117.887 29.9282 118.046 29.7389 118.263 29.6562C118.449 29.5991 118.646 29.5862 118.838 29.6187C119.031 29.6592 119.203 29.7707 119.319 29.9312C119.464 30.2958 119.544 30.6828 119.556 31.0749C119.573 30.7316 119.632 30.3916 119.731 30.0624C119.836 29.9153 119.988 29.8092 120.163 29.7624C120.369 29.7247 120.581 29.7247 120.788 29.7624C120.957 29.8191 121.105 29.9259 121.213 30.0687C121.345 30.4002 121.425 30.7502 121.45 31.1062C121.45 31.1937 121.494 30.8624 121.631 30.6437C121.703 30.4316 121.882 30.2737 122.101 30.2295C122.321 30.1853 122.547 30.2616 122.695 30.4295C122.843 30.5974 122.89 30.8316 122.819 31.0437C122.819 31.4499 122.819 31.4312 122.819 31.7062C122.819 31.9812 122.819 32.2249 122.819 32.4562C122.796 32.8219 122.746 33.1854 122.669 33.5437C122.56 33.8606 122.409 34.1613 122.219 34.4374C121.915 34.7749 121.665 35.1563 121.475 35.5687C121.428 35.7736 121.407 35.9836 121.413 36.1937C121.412 36.3879 121.437 36.5812 121.488 36.7687C121.232 36.7957 120.974 36.7957 120.719 36.7687C120.475 36.7312 120.175 36.2437 120.094 36.0937C120.054 36.0132 119.971 35.9622 119.881 35.9622C119.791 35.9622 119.709 36.0132 119.669 36.0937C119.531 36.3312 119.225 36.7624 119.013 36.7874C118.594 36.8374 117.731 36.7874 117.05 36.7874C117.05 36.7874 117.163 36.1624 116.906 35.9374C116.65 35.7124 116.388 35.4499 116.194 35.2749L115.669 34.6749Z" stroke="black" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M120.969 35.0162V32.8588C120.969 32.7297 120.864 32.625 120.734 32.625C120.605 32.625 120.5 32.7297 120.5 32.8588V35.0162C120.5 35.1453 120.605 35.25 120.734 35.25C120.864 35.25 120.969 35.1453 120.969 35.0162Z" fill="black"/>
<path d="M119.731 35.0154L119.719 32.8569C119.718 32.7281 119.612 32.6243 119.483 32.625C119.354 32.6258 119.249 32.7308 119.25 32.8596L119.263 35.0181C119.263 35.1469 119.369 35.2508 119.498 35.25C119.628 35.2493 119.732 35.1442 119.731 35.0154Z" fill="black"/>
<path d="M118 32.8619L118.013 35.0159C118.013 35.1459 118.119 35.2508 118.248 35.25C118.378 35.2493 118.482 35.1432 118.481 35.0131L118.469 32.8591C118.468 32.7291 118.362 32.6243 118.233 32.625C118.104 32.6258 117.999 32.7318 118 32.8619Z" fill="black"/>
</g>
<path d="M163.451 0.283691H2.54954C1.29794 0.283691 0.283325 1.29978 0.283325 2.55319V77.4468C0.283325 78.7002 1.29794 79.7163 2.54954 79.7163H163.451C164.702 79.7163 165.717 78.7002 165.717 77.4468V2.55319C165.717 1.29978 164.702 0.283691 163.451 0.283691Z" stroke="#E7E7E9"/>
<defs>
<filter id="filter0_dd_784_33028" x="0.324219" y="3.32983" width="11.2491" height="13.2554" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="5"/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_784_33028"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_784_33028" result="effect2_dropShadow_784_33028"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_784_33028" result="shape"/>
</filter>
<filter id="filter1_dd_784_33028" x="0.324219" y="14.6774" width="11.2491" height="13.2554" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="5"/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_784_33028"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_784_33028" result="effect2_dropShadow_784_33028"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_784_33028" result="shape"/>
</filter>
<filter id="filter2_dd_784_33028" x="0.324219" y="26.0248" width="11.2491" height="13.2554" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="5"/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_784_33028"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_784_33028" result="effect2_dropShadow_784_33028"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_784_33028" result="shape"/>
</filter>
<filter id="filter3_dd_784_33028" x="0.324219" y="37.3723" width="11.2491" height="13.2554" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="5"/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_784_33028"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_784_33028" result="effect2_dropShadow_784_33028"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_784_33028" result="shape"/>
</filter>
<filter id="filter4_dd_784_33028" x="0.324219" y="48.7198" width="11.2491" height="13.2554" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="5"/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_784_33028"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_784_33028" result="effect2_dropShadow_784_33028"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_784_33028" result="shape"/>
</filter>
<filter id="filter5_dd_784_33028" x="0.324219" y="60.0674" width="11.2491" height="13.2554" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="5"/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_784_33028"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_784_33028" result="effect2_dropShadow_784_33028"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_784_33028" result="shape"/>
</filter>
<filter id="filter6_dd_784_33028" x="0.324219" y="71.4149" width="11.2491" height="13.2554" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="5"/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_784_33028"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_784_33028" result="effect2_dropShadow_784_33028"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_784_33028" result="shape"/>
</filter>
<filter id="filter7_d_784_33028" x="112.679" y="26.8667" width="11.3476" height="12.118" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.4"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_784_33028"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_784_33028" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

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

@ -1,9 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import NcModal from '../nc/Modal.vue'
import { import {
Modal as AModal,
ActiveCellInj, ActiveCellInj,
EditModeInj, EditModeInj,
IsFormInj, IsFormInj,
JsonExpandInj,
ReadonlyInj, ReadonlyInj,
computed, computed,
inject, inject,
@ -41,7 +42,7 @@ const localValueState = ref<string | undefined>()
const error = ref<string | undefined>() const error = ref<string | undefined>()
const isExpanded = ref(false) const isExpanded = inject(JsonExpandInj, ref(false))
const localValue = computed<string | Record<string, any> | undefined>({ const localValue = computed<string | Record<string, any> | undefined>({
get: () => localValueState.value, get: () => localValueState.value,
@ -139,7 +140,7 @@ useSelectedCellKeyupListener(active, (e) => {
</script> </script>
<template> <template>
<component :is="isExpanded ? AModal : 'div'" v-model:visible="isExpanded" :closable="false" centered :footer="null"> <component :is="isExpanded ? NcModal : 'div'" v-model:visible="isExpanded" :closable="false" centered :footer="null">
<div v-if="editEnabled && !readonly" class="flex flex-col w-full" @mousedown.stop @mouseup.stop @click.stop> <div v-if="editEnabled && !readonly" class="flex flex-col w-full" @mousedown.stop @mouseup.stop @click.stop>
<div class="flex flex-row justify-between pt-1 pb-2" @mousedown.stop> <div class="flex flex-row justify-between pt-1 pb-2" @mousedown.stop>
<a-button type="text" size="small" @click="isExpanded = !isExpanded"> <a-button type="text" size="small" @click="isExpanded = !isExpanded">
@ -148,7 +149,7 @@ useSelectedCellKeyupListener(active, (e) => {
<CilFullscreen v-else class="h-2.5" /> <CilFullscreen v-else class="h-2.5" />
</a-button> </a-button>
<div v-if="!isForm || isExpanded" class="flex flex-row"> <div v-if="!isForm || isExpanded" class="flex flex-row my-1">
<a-button type="text" size="small" :onclick="clear"><div class="text-xs">Cancel</div></a-button> <a-button type="text" size="small" :onclick="clear"><div class="text-xs">Cancel</div></a-button>
<a-button type="primary" size="small" :disabled="!!error || localValue === vModel" @click="onSave"> <a-button type="primary" size="small" :disabled="!!error || localValue === vModel" @click="onSave">

1
packages/nc-gui/components/dashboard/TreeView/TableNode.vue

@ -229,7 +229,6 @@ const isTableOpened = computed(() => {
{{ table.title }} {{ table.title }}
</span> </span>
<div class="flex flex-grow h-full"></div> <div class="flex flex-grow h-full"></div>
<div class="flex flex-row items-center"> <div class="flex flex-row items-center">
<NcDropdown <NcDropdown
v-if=" v-if="

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

@ -12,6 +12,7 @@ interface Props {
interface SubTabGroup { interface SubTabGroup {
[key: string]: { [key: string]: {
key: string
title: string title: string
body: any body: any
onClick?: () => void onClick?: () => void
@ -114,6 +115,7 @@ const tabsInfo: TabGroup = {
subTabs: { subTabs: {
misc: { misc: {
// Misc // Misc
key: 'Misc',
title: t('general.misc'), title: t('general.misc'),
body: Misc, body: Misc,
}, },
@ -254,7 +256,7 @@ watch(
v-model:state="vDataState" v-model:state="vDataState"
v-model:reload="dataSourcesReload" v-model:reload="dataSourcesReload"
class="px-2 pb-2" class="px-2 pb-2"
:data-testid="`nc-settings-subtab-${selectedSubTab.title}`" :data-testid="`nc-settings-subtab-${selectedSubTab.key}`"
:project-id="projectId" :project-id="projectId"
@awaken="handleAwaken" @awaken="handleAwaken"
/> />
@ -263,7 +265,7 @@ watch(
v-else v-else
class="px-2 py-6" class="px-2 py-6"
:project-id="projectId" :project-id="projectId"
:data-testid="`nc-settings-subtab-${selectedSubTab.title}`" :data-testid="`nc-settings-subtab-${selectedSubTab.key}`"
/> />
</div> </div>
</a-layout-content> </a-layout-content>

4
packages/nc-gui/components/monaco/Editor.vue

@ -90,8 +90,8 @@ onMounted(async () => {
foldingStrategy: 'indentation', foldingStrategy: 'indentation',
selectOnLineNumbers: true, selectOnLineNumbers: true,
scrollbar: { scrollbar: {
verticalScrollbarSize: 8, verticalScrollbarSize: 1,
horizontalScrollbarSize: 8, horizontalScrollbarSize: 1,
}, },
tabSize: 2, tabSize: 2,
automaticLayout: true, automaticLayout: true,

3
packages/nc-gui/components/smartsheet/Details.vue

@ -16,6 +16,9 @@ const openedSubTab = computed({
watch( watch(
openedSubTab, openedSubTab,
() => { () => {
if (openedSubTab.value === 'field' && !isUIAllowed('hookList')) {
onViewsTabChange('relation')
}
if (openedSubTab.value === 'webhook' && !isUIAllowed('hookList')) { if (openedSubTab.value === 'webhook' && !isUIAllowed('hookList')) {
onViewsTabChange('relation') onViewsTabChange('relation')
} }

852
packages/nc-gui/components/smartsheet/details/Fields.vue

@ -0,0 +1,852 @@
<script setup lang="ts">
import { diff } from 'deep-object-diff'
import { message } from 'ant-design-vue'
import { UITypes, isSystemColumn } from 'nocodb-sdk'
import Draggable from 'vuedraggable'
import type { ColumnType, SelectOptionsType, TableType } from 'nocodb-sdk'
import { Icon } from '@iconify/vue'
import { type Field, getUniqueColumnName, ref, useSmartsheetStoreOrThrow } from '#imports'
interface TableExplorerColumn extends ColumnType {
id?: string
temp_id?: string
column_order?: {
order: number
view_id: string
}
}
interface op {
op: 'add' | 'update' | 'delete'
column: TableExplorerColumn
}
interface fieldsVisibilityOps {
visible: boolean
column: TableExplorerColumn
}
interface moveOp {
op: 'move'
column: TableExplorerColumn
index: number
order: number
}
const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const { meta, view } = useSmartsheetStoreOrThrow()
const moveOps = ref<moveOp[]>([])
const visibilityOps = ref<fieldsVisibilityOps[]>([])
const selectedView = inject(ActiveViewInj)
const { fields: viewFields, toggleFieldVisibility, loadViewColumns } = useViewColumns(view, meta as Ref<TableType | undefined>)
const loading = ref(false)
const columnsHash = ref<string>()
const newFields = ref<TableExplorerColumn[]>([])
const compareCols = (a?: TableExplorerColumn, b?: TableExplorerColumn) => {
if (a?.id && b?.id) {
return a.id === b.id
} else if (a?.temp_id && b?.temp_id) {
return a.temp_id === b.temp_id
}
return false
}
const viewFieldsMap = computed<Record<string, Field>>(() => {
const temp: Record<string, Field> = {}
if (viewFields.value) {
for (const field of viewFields.value) {
if (field.fk_column_id) temp[field.fk_column_id] = field
}
}
return temp
})
const getFieldOrder = (field?: TableExplorerColumn) => {
if (!field) return -1
const mop = moveOps.value.find((op) => compareCols(op.column, field))
if (mop) {
return mop.order
} else if (field.id) {
const viewField = viewFieldsMap.value[field.id]
if (viewField) {
return viewField.order
}
}
return -1
}
const fields = computed<TableExplorerColumn[]>({
get: () => {
const x = (meta.value?.columns as ColumnType[])
.filter((field) => !field.fk_column_id && !isSystemColumn(field))
.concat(newFields.value)
.sort((a, b) => {
return getFieldOrder(a) - getFieldOrder(b)
})
return x
},
set: (val) => {
meta.value!.columns = meta.value?.columns?.map((col) => {
const field = val.find((f) => compareCols(f, col))
if (field) {
return field
}
return col
})
},
})
// Current Selected Field
const activeField = ref()
const searchQuery = ref<string>('')
const calculateOrderForIndex = (index: number, fromAbove = false) => {
if (!viewFields.value) return -1
if (index <= 0) {
const pv = fields.value.find((f) => f.pv)
if (pv) {
return pv.order || 0
}
return -1
}
if (index >= fields.value.length - 1) {
const fieldOrders = fields.value.map((f) => getFieldOrder(f))
return Math.max(...fieldOrders) + 1
}
let orderBefore = -1
let orderAfter = -1
const fieldBefore = fields.value[index + (fromAbove ? -1 : 0)]
const fieldAfter = fields.value[index + (fromAbove ? 0 : 1)]
if (fieldBefore) {
orderBefore = getFieldOrder(fieldBefore)
}
if (fieldAfter) {
orderAfter = getFieldOrder(fieldAfter)
if (orderAfter === -1) {
orderAfter = orderBefore + 1
}
}
const order = (orderBefore + orderAfter) / 2
return order
}
// Update, Delete and New Column operations are tracked here
const ops = ref<op[]>([])
const temporaryAddCount = ref(0)
const changingField = ref(false)
const addFieldMoveHook = ref<number>()
const duplicateFieldHook = ref<TableExplorerColumn>()
const setFieldMoveHook = (field: TableExplorerColumn, before = false) => {
const index = fields.value.findIndex((f) => compareCols(f, field))
if (index !== -1) {
addFieldMoveHook.value = before ? index : index + 1
}
}
const changeField = (field?: TableExplorerColumn, event?: MouseEvent) => {
if (event) {
if (event.target instanceof HTMLElement) {
if (event.target.closest('.no-action')) return
}
}
if (compareCols(field, activeField.value) || (field === undefined && activeField.value === undefined)) return
changingField.value = true
nextTick(() => {
activeField.value = field
changingField.value = false
})
}
const addField = (field?: TableExplorerColumn, before = false) => {
if (field) {
setFieldMoveHook(field, before)
}
changeField({})
}
const displayColumn = computed(() => {
if (!meta.value?.columns) return
return meta.value?.columns.find((col) => col.pv)
})
const duplicateField = async (field: TableExplorerColumn) => {
if (!meta.value?.columns) return
// generate duplicate column name
const duplicateColumnName = getUniqueColumnName(`${field.title}_copy`, meta.value?.columns)
let fieldPayload = {}
// construct column create payload
switch (field.uidt) {
case UITypes.LinkToAnotherRecord:
case UITypes.Links:
case UITypes.Lookup:
case UITypes.Rollup:
case UITypes.Formula:
return message.info('Not available at the moment')
case UITypes.SingleSelect:
case UITypes.MultiSelect:
fieldPayload = {
...field,
title: duplicateColumnName,
column_name: duplicateColumnName,
id: undefined,
order: undefined,
pv: false,
colOptions: {
options:
(field.colOptions as SelectOptionsType)?.options?.map((option: Record<string, any>) => ({
...option,
id: undefined,
})) ?? [],
},
}
break
default:
fieldPayload = {
...field,
title: duplicateColumnName,
column_name: duplicateColumnName,
id: undefined,
colOptions: undefined,
order: undefined,
pv: false,
}
break
}
addField(field)
duplicateFieldHook.value = fieldPayload as TableExplorerColumn
}
// This method is called whenever there is a change in field properties
const onFieldUpdate = (state: TableExplorerColumn) => {
const col = fields.value.find((col) => compareCols(col, state))
if (!col) return
const diffs = diff(col, state)
if (Object.keys(diffs).length === 0 || (Object.keys(diffs).length === 1 && 'altered' in diffs)) {
ops.value = ops.value.filter((op) => op.op === 'add' || !compareCols(op.column, state))
} else {
const field = ops.value.find((op) => compareCols(op.column, state))
if (field) {
field.column = state
} else {
ops.value.push({
op: 'update',
column: state,
})
}
}
}
const onFieldDelete = (state: TableExplorerColumn) => {
const field = ops.value.find((op) => compareCols(op.column, state))
if (field) {
if (field.op === 'delete') {
ops.value = ops.value.filter((op) => op.column.id !== state.id)
} else if (field.op === 'add') {
if (activeField.value && compareCols(activeField.value, state)) {
changeField()
}
ops.value = ops.value.filter((op) => op.column.temp_id !== state.temp_id)
newFields.value = newFields.value.filter((op) => op.temp_id !== state.temp_id)
} else {
field.op = 'delete'
field.column = state
}
} else {
ops.value.push({
op: 'delete',
column: state,
})
}
}
const onFieldAdd = (state: TableExplorerColumn) => {
if (duplicateFieldHook.value) {
state = duplicateFieldHook.value
duplicateFieldHook.value = undefined
}
state.temp_id = `temp_${++temporaryAddCount.value}`
ops.value.push({
op: 'add',
column: state,
})
newFields.value.push(state)
if (addFieldMoveHook.value) {
moveOps.value.push({
op: 'move',
column: state,
index: addFieldMoveHook.value,
order: calculateOrderForIndex(addFieldMoveHook.value),
})
addFieldMoveHook.value = undefined
} else {
moveOps.value.push({
op: 'move',
column: state,
index: fields.value.length,
order: calculateOrderForIndex(fields.value.length),
})
}
changeField(state)
}
const onMove = (_event: { moved: { newIndex: number; oldIndex: number } }) => {
const order = calculateOrderForIndex(_event.moved.newIndex, _event.moved.newIndex < _event.moved.oldIndex)
const field = fields.value[_event.moved.oldIndex]
const op = ops.value.find((op) => compareCols(op.column, field))
if (op) {
onFieldUpdate({
...op.column,
column_order: {
order,
view_id: view.value?.id as string,
},
})
} else {
onFieldUpdate({
...field,
column_order: {
order,
view_id: view.value?.id as string,
},
})
}
const mop = moveOps.value.find((op) => compareCols(op.column, fields.value[_event.moved.oldIndex]))
if (mop) {
mop.index = _event.moved.newIndex
mop.order = order
} else {
moveOps.value.push({
op: 'move',
column: fields.value[_event.moved.oldIndex],
index: _event.moved.newIndex,
order,
})
}
}
const recoverField = (state: TableExplorerColumn) => {
const field = ops.value.find((op) => compareCols(op.column, state))
if (field) {
if (field.op === 'delete') {
ops.value = ops.value.filter((op) => !compareCols(op.column, state))
} else if (field.op === 'update') {
ops.value = ops.value.filter((op) => !compareCols(op.column, state))
}
activeField.value = null
changeField(fields.value.filter((fiel) => fiel.id === state.id)[0])
}
}
const fieldState = (field: TableExplorerColumn) => {
const col = fields.value.find((col) => compareCols(col, field))
if (col) {
const op = ops.value.find((op) => compareCols(op.column, col))
if (op) {
return op.column
}
}
return null
}
const fieldStatuses = computed<Record<string, string>>(() => {
const statuses: Record<string, string> = {}
for (const op of ops.value) {
if (op.op === 'add') {
if (op.column.temp_id) statuses[op.column.temp_id] = 'add'
} else if (op.op === 'update') {
if (op.column.id) statuses[op.column.id] = 'update'
} else if (op.op === 'delete') {
if (op.column.id) statuses[op.column.id] = 'delete'
}
}
return statuses
})
const fieldStatus = (field?: TableExplorerColumn) => {
const id = field?.id || field?.temp_id
return id ? fieldStatuses.value[id] : ''
}
const clearChanges = () => {
ops.value = []
moveOps.value = []
newFields.value = []
visibilityOps.value = []
changeField()
}
const saveChanges = async () => {
try {
if (!meta.value?.id) return
loading.value = true
for (const mop of moveOps.value) {
const op = ops.value.find((op) => compareCols(op.column, mop.column))
if (op && op.op === 'add') {
op.column.column_order = {
order: mop.order,
view_id: view.value?.id as string,
}
}
}
for (const f of fields.value) {
console.log(f.title, getFieldOrder(f))
}
for (const op of ops.value) {
if (op.op === 'add') {
if (activeField.value && compareCols(activeField.value, op.column)) {
changeField()
}
} else if (op.op === 'delete') {
if (activeField.value && compareCols(activeField.value, op.column)) {
changeField()
}
}
}
const res = await $api.dbTableColumn.bulk(meta.value?.id, {
hash: columnsHash.value,
ops: ops.value,
})
for (const op of visibilityOps.value) {
await toggleFieldVisibility(op.visible, {
...op.column,
show: op.visible,
})
}
await loadViewColumns()
if (res) {
ops.value = (res.failedOps as op[]) || []
newFields.value = newFields.value.filter((col) => {
if (res.failedOps) {
const op = res.failedOps.find((fop) => {
return (fop.column as TableExplorerColumn).temp_id === col.temp_id
})
if (op) {
return true
}
}
return false
})
moveOps.value = []
}
await getMeta(meta.value.id, true)
columnsHash.value = (await $api.dbTableColumn.hash(meta.value?.id)).hash
visibilityOps.value = []
} catch (e) {
message.error('Something went wrong')
} finally {
loading.value = false
}
}
const toggleVisibility = async (checked: boolean, field: Field) => {
if (field.fk_column_id && fieldStatuses.value[field.fk_column_id]) {
message.warning('You cannot change visibility of a field that is being edited. Please save or discard changes first.')
return
}
if (visibilityOps.value.find((op) => op.column.fk_column_id === field.fk_column_id)) {
visibilityOps.value = visibilityOps.value.filter((op) => op.column.fk_column_id !== field.fk_column_id)
return
}
visibilityOps.value.push({
visible: checked,
column: field,
})
}
onMounted(async () => {
if (!meta.value?.id) return
columnsHash.value = (await $api.dbTableColumn.hash(meta.value?.id)).hash
})
</script>
<template>
<div class="flex flex-col items-center w-full p-4" style="height: calc(100vh - (var(--topbar-height) * 2))">
<div class="h-full max-w-250 w-full">
<div class="flex flex-col h-full">
<div class="flex w-full justify-between py-2">
<div class="flex flex-1 items-center gap-2">
<h1 class="font-bold text-base">Fields</h1>
<div class="flex bg-gray-100 items-center mb-1.5 rounded-lg px-2">
<LazyGeneralEmojiPicker :emoji="selectedView?.meta?.icon" readonly size="xsmall">
<template #default>
<GeneralViewIcon :meta="{ type: selectedView?.type }" class="min-w-4.5 text-lg flex" />
</template>
</LazyGeneralEmojiPicker>
<span class="text-sm pl-1.25 text-gray-700">
{{ selectedView?.title }}
</span>
</div>
</div>
<div class="flex gap-2">
<NcButton
type="secondary"
size="small"
:disabled="!loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1"
@click="clearChanges()"
>
Reset
</NcButton>
<NcButton
type="primary"
size="small"
:loading="loading"
:disabled="!loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1"
@click="saveChanges()"
>
Save changes
</NcButton>
</div>
</div>
<div class="flex gap-x-4 overflow-y-auto">
<div class="flex flex-col flex-1 nc-scrollbar-md">
<div class="flex w-full justify-between pb-2 pr-1">
<a-input v-model:value="searchQuery" class="!h-8 !px-1 !rounded-lg !w-3/6" placeholder="Search field">
<template #prefix>
<GeneralIcon icon="search" class="mx-1 h-3.5 w-3.5 text-gray-500 group-hover:text-black" />
</template>
<template #suffix>
<GeneralIcon
v-if="searchQuery.length > 0"
icon="close"
class="mx-1 h-3.5 w-3.5 text-gray-500 group-hover:text-black"
@click="searchQuery = ''"
/>
</template>
</a-input>
<NcButton type="secondary" size="small" :disabled="loading" @click="addField()">
<div class="flex items-center gap-2">
<GeneralIcon icon="plus" class="h-3.5 mb-1 w-3.5" />
New field
</div>
</NcButton>
</div>
<Draggable v-model="fields" item-key="id" @change="onMove($event)">
<template #item="{ element: field }">
<div
v-if="field.title && field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv"
class="flex px-2 mr-1 border-x-1 bg-white border-t-1 hover:bg-gray-100 first:rounded-t-lg last:border-b-1 last:rounded-b-lg pl-5 group"
:class="` ${compareCols(field, activeField) ? 'selected' : ''}`"
@click="changeField(field, $event)"
>
<div class="flex items-center flex-1 py-2.5 gap-1 w-2/6">
<component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-600 mr-1" />
<NcCheckbox
v-if="field.id && viewFieldsMap[field.id]"
:checked="
visibilityOps.find((op) => op.column.fk_column_id === field.id)?.visible ?? viewFieldsMap[field.id].show
"
@change="
(event) => {
toggleVisibility(event.target.checked, viewFieldsMap[field.id])
}
"
/>
<NcCheckbox v-else :disabled="true" class="opacity-0" :checked="true" />
<SmartsheetHeaderCellIcon
v-if="field"
:column-meta="fieldState(field) || field"
:class="{
'text-brand-500': compareCols(field, activeField),
}"
/>
<span
:class="{
'text-brand-500': compareCols(field, activeField),
}"
class="truncate max-w-64"
>
{{ fieldState(field)?.title || field.title }}
</span>
</div>
<div class="flex items-center justify-end gap-1">
<div class="flex items-center">
<NcBadge v-if="fieldStatus(field) === 'delete'" color="red" :border="false" class="bg-red-50 text-red-700">
Deleted field
</NcBadge>
<NcBadge
v-else-if="fieldStatus(field) === 'add'"
color="orange"
:border="false"
class="bg-green-50 text-green-700"
>
New field
</NcBadge>
<NcBadge
v-else-if="fieldStatus(field) === 'update'"
color="orange"
:border="false"
class="bg-orange-50 text-orange-700"
>
Updated field
</NcBadge>
</div>
<NcButton
v-if="fieldStatus(field) === 'delete' || fieldStatus(field) === 'update'"
type="secondary"
size="small"
class="no-action mr-2"
:disabled="loading"
@click="recoverField(field)"
>
<div class="flex items-center text-xs gap-1">
<GeneralIcon icon="reload" />
Restore
</div>
</NcButton>
<a-dropdown v-else :trigger="['click']" overlay-class-name="nc-dropdown-table-explorer" @click.stop>
<GeneralIcon icon="threeDotVertical" class="no-action opacity-0 group-hover:(opacity-100) text-gray-500" />
<template #overlay>
<a-menu>
<a-menu-item key="table-explorer-duplicate" @click="duplicateField(field)">
<div class="nc-project-menu-item">
<Icon class="iconify text-gray-800" icon="lucide:copy" /><span>Duplicate</span>
</div>
</a-menu-item>
<a-menu-item v-if="!field.pv" key="table-explorer-insert-above" @click="addField(field, true)">
<div class="nc-project-menu-item">
<Icon class="iconify text-gray-800" icon="lucide:arrow-up" /><span>Insert above</span>
</div>
</a-menu-item>
<a-menu-item key="table-explorer-insert-below" @click="addField(field)">
<div class="nc-project-menu-item">
<Icon class="iconify text-gray-800" icon="lucide:arrow-down" /><span>Insert below</span>
</div>
</a-menu-item>
<a-menu-divider class="my-0" />
<a-menu-item key="table-explorer-delete" @click="onFieldDelete(field)">
<div class="nc-project-menu-item group text-red-500">
<GeneralIcon icon="delete" class="group-hover:text-accent" />
Delete
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<MdiChevronRight
class="text-brand-500 opacity-0"
:class="{
'opacity-100': compareCols(field, activeField),
}"
/>
</div>
</div>
</template>
<template v-if="displayColumn && displayColumn.title.toLowerCase().includes(searchQuery.toLowerCase())" #header>
<div
class="flex px-2 mr-1 border-x-1 bg-white border-t-1 hover:bg-gray-100 first:rounded-t-lg last:border-b-1 last:rounded-b-lg pl-5 group"
:class="` ${compareCols(displayColumn, activeField) ? 'selected' : ''}`"
@click="changeField(displayColumn, $event)"
>
<div class="flex items-center flex-1 py-2.5 gap-1 w-2/6">
<component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-200 mr-1" />
<NcCheckbox :disabled="true" :checked="true" />
<SmartsheetHeaderCellIcon
v-if="displayColumn"
:column-meta="fieldState(displayColumn) || displayColumn"
:class="{
'text-brand-500': compareCols(displayColumn, activeField),
}"
/>
<span
:class="{
'text-brand-500': compareCols(displayColumn, activeField),
}"
>
{{ fieldState(displayColumn)?.title || displayColumn.title }}
</span>
</div>
<div class="flex items-center justify-end gap-1">
<div class="flex items-center">
<NcBadge
v-if="fieldStatus(displayColumn) === 'delete'"
color="red"
:border="false"
class="bg-red-50 text-red-700"
>
Deleted field
</NcBadge>
<NcBadge
v-else-if="fieldStatus(displayColumn) === 'update'"
color="orange"
:border="false"
class="bg-orange-50 text-orange-700"
>
Updated field
</NcBadge>
</div>
<NcButton
v-if="fieldStatus(displayColumn) === 'delete' || fieldStatus(displayColumn) === 'update'"
type="secondary"
size="small"
class="no-action mr-2"
:disabled="loading"
@click="recoverField(displayColumn)"
>
<div class="flex items-center text-xs gap-1">
<GeneralIcon icon="reload" />
Restore
</div>
</NcButton>
<a-dropdown v-else :trigger="['click']" overlay-class-name="nc-dropdown-table-explorer" @click.stop>
<GeneralIcon icon="threeDotVertical" class="no-action opacity-0 group-hover:(opacity-100) text-gray-500" />
<template #overlay>
<a-menu>
<a-menu-item key="table-explorer-duplicate" @click="duplicateField(displayColumn)">
<div class="nc-project-menu-item">
<Icon class="iconify text-gray-800" icon="lucide:copy" /><span>Duplicate</span>
</div>
</a-menu-item>
<a-menu-item v-if="!field.pv" key="table-explorer-insert-above" @click="addField(displayColumn, true)">
<div class="nc-project-menu-item">
<Icon class="iconify text-gray-800" icon="lucide:arrow-up" /><span>Insert above</span>
</div>
</a-menu-item>
<a-menu-item key="table-explorer-insert-below" @click="addField(displayColumn)">
<div class="nc-project-menu-item">
<Icon class="iconify text-gray-800" icon="lucide:arrow-down" /><span>Insert below</span>
</div>
</a-menu-item>
<a-menu-divider class="my-0" />
<a-menu-item key="table-explorer-delete" @click="onFieldDelete(displayColumn)">
<div class="nc-project-menu-item group text-red-500">
<GeneralIcon icon="delete" class="group-hover:text-accent" />
Delete
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<MdiChevronRight
class="text-brand-500 opacity-0"
:class="{
'opacity-100': compareCols(displayColumn, activeField),
}"
/>
</div>
</div>
</template>
</Draggable>
</div>
<Transition v-if="!changingField" name="slide-fade">
<div class="flex p-4 h-fit w-1/3 border-gray-200 border-1 rounded-xl">
<SmartsheetColumnEditOrAddProvider
v-if="activeField"
class="w-full"
:column="activeField"
:preload="fieldState(activeField)"
:table-explorer-columns="fields"
embed-mode
from-table-explorer
@update="onFieldUpdate"
@add="onFieldAdd"
/>
<div v-else class="flex flex-col gap-6 w-full items-center">
<img src="~assets/img/fieldPlaceholder.svg" class="!w-[18rem]" />
<div class="text-2xl text-gray-600 font-bold text-center">Select a field</div>
<div class="text-center text-sm px-2 text-gray-500">
Make changes to field properties by selecting a field from the list
</div>
</div>
</div>
</Transition>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.add {
background-color: #e6ffed !important;
border-color: #b7eb8f;
}
.update {
background-color: #fffbe6 !important;
border-color: #ffe58f;
}
.delete {
background-color: #fff1f0 !important;
border-color: #ffa39e;
}
.selected {
@apply bg-brand-50;
}
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.5s cubic-bezier(1, 0.5, 0.8, 1);
}
.skip-animation {
transition: none;
}
.slide-fade-enter-from {
transform: translateX(20px);
opacity: 0;
}
.slide-fade-leave-to {
opacity: 0;
}
</style>

5
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -8,6 +8,7 @@ import {
FieldsInj, FieldsInj,
IsGroupByInj, IsGroupByInj,
IsLockedInj, IsLockedInj,
JsonExpandInj,
MetaInj, MetaInj,
NavigateDir, NavigateDir,
ReadonlyInj, ReadonlyInj,
@ -225,6 +226,9 @@ const showContextMenu = (e: MouseEvent, target?: { row: number; col: number }) =
} }
} }
const isJsonExpand = ref(false)
provide(JsonExpandInj, isJsonExpand)
// #Cell - 1 // #Cell - 1
async function clearCell(ctx: { row: number; col: number } | null, skipUpdate = false) { async function clearCell(ctx: { row: number; col: number } | null, skipUpdate = false) {
@ -1316,6 +1320,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
v-if="addColumnDropdown" v-if="addColumnDropdown"
:preload="preloadColumn" :preload="preloadColumn"
:column-position="columnOrder" :column-position="columnOrder"
:class="{ hidden: isJsonExpand }"
@submit="closeAddColumnDropdown(true)" @submit="closeAddColumnDropdown(true)"
@cancel="closeAddColumnDropdown()" @cancel="closeAddColumnDropdown()"
@click.stop @click.stop

4
packages/nc-gui/components/smartsheet/grid/newFile.ts

@ -0,0 +1,4 @@
import { JsonExpandInj, provide } from '#imports';
import { isJsonExpand } from './Table.vue';
provide(JsonExpandInj, isJsonExpand);

2
packages/nc-gui/components/smartsheet/topbar/SelectMode.vue

@ -24,7 +24,7 @@ const { onViewsTabChange } = useViewsStore()
:class="{ :class="{
active: openedViewsTab !== 'view', active: openedViewsTab !== 'view',
}" }"
@click="onViewsTabChange(isEeUI ? 'field' : 'relation')" @click="onViewsTabChange('field')"
> >
<GeneralIcon <GeneralIcon
icon="erd" icon="erd"

4
packages/nc-gui/composables/useApi/interceptors.ts

@ -37,7 +37,9 @@ export function addAxiosInterceptors(api: Api<any>) {
// Return a successful response back to the calling service // Return a successful response back to the calling service
api.instance.interceptors.response.use( api.instance.interceptors.response.use(
(response) => response, (response) => {
return response
},
// Handle Error // Handle Error
(error) => { (error) => {
if (error.response && error.response.data && error.response.data.msg === DbNotFoundMsg) return router.replace('/project/0') if (error.response && error.response.data && error.response.data.msg === DbNotFoundMsg) return router.replace('/project/0')

4
packages/nc-gui/composables/useViewFilters.ts

@ -447,10 +447,6 @@ export function useViewFilters(
const lookupRelation = (await getMeta(nextCol.fk_model_id))?.columns?.find( const lookupRelation = (await getMeta(nextCol.fk_model_id))?.columns?.find(
(c) => c.id === (nextCol.colOptions as LookupType).fk_relation_column_id, (c) => c.id === (nextCol.colOptions as LookupType).fk_relation_column_id,
) )
if ((lookupRelation.colOptions as LinkToAnotherRecordType).type !== RelationTypes.BELONGS_TO) {
btLookup = false
continue
}
const relatedTableMeta = await getMeta((lookupRelation.colOptions as LinkToAnotherRecordType).fk_related_model_id) const relatedTableMeta = await getMeta((lookupRelation.colOptions as LinkToAnotherRecordType).fk_related_model_id)
nextCol = relatedTableMeta?.columns?.find((c) => c.id === (nextCol.colOptions as LookupType).fk_lookup_column_id) nextCol = relatedTableMeta?.columns?.find((c) => c.id === (nextCol.colOptions as LookupType).fk_lookup_column_id)

1
packages/nc-gui/context/index.ts

@ -50,3 +50,4 @@ export const TreeViewInj: InjectionKey<{
openRenameTableDialog: (table: TableType, rightClick: boolean) => void openRenameTableDialog: (table: TableType, rightClick: boolean) => void
contextMenuTarget: { type?: 'project' | 'base' | 'table' | 'main' | 'layout'; value?: any } contextMenuTarget: { type?: 'project' | 'base' | 'table' | 'main' | 'layout'; value?: any }
}> = Symbol('tree-view-functions-injection') }> = Symbol('tree-view-functions-injection')
export const JsonExpandInj: InjectionKey<Ref<boolean>> = Symbol('json-expand-injection')

2
packages/nc-gui/pages/index/[typeOrId]/view/[viewId].vue

@ -13,6 +13,8 @@ const route = useRoute()
const { loadSharedView, meta } = useSharedView() const { loadSharedView, meta } = useSharedView()
const { isViewDataLoading } = storeToRefs(useViewsStore()) const { isViewDataLoading } = storeToRefs(useViewsStore())
provide(MetaInj, meta)
const showPassword = ref(false) const showPassword = ref(false)
onMounted(async () => { onMounted(async () => {

18
packages/nc-gui/store/views.ts

@ -156,6 +156,12 @@ export const useViewsStore = defineStore('viewsStore', () => {
}) => { }) => {
const routeName = 'index-typeOrId-projectId-index-index-viewId-viewTitle' const routeName = 'index-typeOrId-projectId-index-index-viewId-viewTitle'
let projectIdOrBaseId = projectId
if (['base'].includes(route.value.params.typeOrId as string)) {
projectIdOrBaseId = route.value.params.projectId as string
}
if ( if (
router.currentRoute.value.query && router.currentRoute.value.query &&
router.currentRoute.value.query.page && router.currentRoute.value.query.page &&
@ -163,11 +169,11 @@ export const useViewsStore = defineStore('viewsStore', () => {
) { ) {
await router.push({ await router.push({
name: routeName, name: routeName,
params: { viewTitle: view.id || '', viewId: tableId, projectId }, params: { viewTitle: view.id || '', viewId: tableId, projectId: projectIdOrBaseId },
query: router.currentRoute.value.query, query: router.currentRoute.value.query,
}) })
} else { } else {
await router.push({ name: routeName, params: { viewTitle: view.id || '', viewId: tableId, projectId } }) await router.push({ name: routeName, params: { viewTitle: view.id || '', viewId: tableId, projectId: projectIdOrBaseId } })
} }
if (hardReload) { if (hardReload) {
@ -175,10 +181,14 @@ export const useViewsStore = defineStore('viewsStore', () => {
.replace({ .replace({
name: routeName, name: routeName,
query: { reload: 'true' }, query: { reload: 'true' },
params: { viewId: tableId, projectId, viewTitle: view.id || '' }, params: { viewId: tableId, projectId: projectIdOrBaseId, viewTitle: view.id || '' },
}) })
.then(() => { .then(() => {
router.replace({ name: routeName, query: {}, params: { viewId: tableId, viewTitle: view.id || '', projectId } }) router.replace({
name: routeName,
query: {},
params: { viewId: tableId, viewTitle: view.id || '', projectId: projectIdOrBaseId },
})
}) })
} }
} }

19
packages/nocodb/src/db/conditionV2.ts

@ -100,7 +100,11 @@ const parseConditionV2 = async (
const parentModel = await parentColumn.getModel(); const parentModel = await parentColumn.getModel();
await parentModel.getColumns(); await parentModel.getColumns();
if (colOptions.type === RelationTypes.HAS_MANY) { if (colOptions.type === RelationTypes.HAS_MANY) {
if (['blank', 'notblank'].includes(filter.comparison_op)) { if (
['blank', 'notblank', 'checked', 'notchecked'].includes(
filter.comparison_op,
)
) {
// handle self reference // handle self reference
if (parentModel.id === childModel.id) { if (parentModel.id === childModel.id) {
if (filter.comparison_op === 'blank') { if (filter.comparison_op === 'blank') {
@ -158,7 +162,11 @@ const parseConditionV2 = async (
else qbP.whereIn(parentColumn.column_name, selectQb); else qbP.whereIn(parentColumn.column_name, selectQb);
}; };
} else if (colOptions.type === RelationTypes.BELONGS_TO) { } else if (colOptions.type === RelationTypes.BELONGS_TO) {
if (['blank', 'notblank'].includes(filter.comparison_op)) { if (
['blank', 'notblank', 'checked', 'notchecked'].includes(
filter.comparison_op,
)
) {
// handle self reference // handle self reference
if (parentModel.id === childModel.id) { if (parentModel.id === childModel.id) {
if (filter.comparison_op === 'blank') { if (filter.comparison_op === 'blank') {
@ -221,7 +229,11 @@ const parseConditionV2 = async (
const mmParentColumn = await colOptions.getMMParentColumn(); const mmParentColumn = await colOptions.getMMParentColumn();
const mmChildColumn = await colOptions.getMMChildColumn(); const mmChildColumn = await colOptions.getMMChildColumn();
if (['blank', 'notblank'].includes(filter.comparison_op)) { if (
['blank', 'notblank', 'checked', 'notchecked'].includes(
filter.comparison_op,
)
) {
// handle self reference // handle self reference
if (mmModel.id === childModel.id) { if (mmModel.id === childModel.id) {
if (filter.comparison_op === 'blank') { if (filter.comparison_op === 'blank') {
@ -815,6 +827,7 @@ const negatedMapping = {
nlike: { comparison_op: 'like' }, nlike: { comparison_op: 'like' },
neq: { comparison_op: 'eq' }, neq: { comparison_op: 'eq' },
blank: { comparison_op: 'notblank' }, blank: { comparison_op: 'notblank' },
notchecked: { comparison_op: 'checked' },
}; };
function getAlias(aliasCount: { count: number }) { function getAlias(aliasCount: { count: number }) {

80
packages/nocodb/src/services/datas.service.ts

@ -1,16 +1,11 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { isSystemColumn, UITypes } from 'nocodb-sdk'; import { isSystemColumn } from 'nocodb-sdk';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
import papaparse from 'papaparse'; import papaparse from 'papaparse';
import { nocoExecute } from 'nc-help'; import { nocoExecute } from 'nc-help';
import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2'; import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2';
import type { PathParams } from '~/modules/datas/helpers'; import type { PathParams } from '~/modules/datas/helpers';
import type { LinkToAnotherRecordColumn, LookupColumn } from '~/models'; import { getDbRows, getViewAndModelByAliasOrId } from '~/modules/datas/helpers';
import {
getDbRows,
getViewAndModelByAliasOrId,
serializeCellValue,
} from '~/modules/datas/helpers';
import { Base, Column, Model, Project, View } from '~/models'; import { Base, Column, Model, Project, View } from '~/models';
import { NcError } from '~/helpers/catchError'; import { NcError } from '~/helpers/catchError';
import getAst from '~/helpers/getAst'; import getAst from '~/helpers/getAst';
@ -946,77 +941,6 @@ export class DatasService {
return { offset, dbRows, elapsed, data }; return { offset, dbRows, elapsed, data };
} }
async serializeCellValue({
value,
column,
siteUrl,
}: {
column?: Column;
value: any;
siteUrl: string;
}) {
if (!column) {
return value;
}
if (!value) return value;
switch (column?.uidt) {
case UITypes.Attachment: {
let data = value;
try {
if (typeof value === 'string') {
data = JSON.parse(value);
}
} catch {}
return (data || []).map(
(attachment) =>
`${encodeURI(attachment.title)}(${encodeURI(
attachment.path
? `${siteUrl}/${attachment.path}`
: attachment.url,
)})`,
);
}
case UITypes.Lookup:
{
const colOptions = await column.getColOptions<LookupColumn>();
const lookupColumn = await colOptions.getLookupColumn();
return (
await Promise.all(
[...(Array.isArray(value) ? value : [value])].map(async (v) =>
serializeCellValue({
value: v,
column: lookupColumn,
siteUrl,
}),
),
)
).join(', ');
}
break;
case UITypes.LinkToAnotherRecord:
{
const colOptions =
await column.getColOptions<LinkToAnotherRecordColumn>();
const relatedModel = await colOptions.getRelatedTable();
await relatedModel.getColumns();
return [...(Array.isArray(value) ? value : [value])]
.map((v) => {
return v[relatedModel.displayValue?.title];
})
.join(', ');
}
break;
default:
if (value && typeof value === 'object') {
return JSON.stringify(value);
}
return value;
}
}
async getColumnByIdOrName(columnNameOrId: string, model: Model) { async getColumnByIdOrName(columnNameOrId: string, model: Model) {
const column = (await model.getColumns()).find( const column = (await model.getColumns()).find(
(c) => (c) =>

133
packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts

@ -1224,99 +1224,6 @@ function numberBased() {
}, },
]; ];
const recordsPg = [
{
Id: 1,
Number: 33,
Decimal: 33.3,
Currency: 33.3,
Percent: 33,
Duration: 10,
Rating: 0,
},
{
Id: 2,
Number: null,
Decimal: 456.34,
Currency: 456.34,
Percent: null,
Duration: 20,
Rating: 1,
},
{
Id: 3,
Number: 456,
Decimal: 333.3,
Currency: 333.3,
Percent: 456,
Duration: 30,
Rating: 2,
},
{
Id: 4,
Number: 333,
Decimal: null,
Currency: null,
Percent: 333,
Duration: 40,
Rating: 3,
},
{
Id: 5,
Number: 267,
Decimal: 267.5674,
Currency: 267.5674,
Percent: 267,
Duration: 50,
Rating: null,
},
{
Id: 6,
Number: 34,
Decimal: 34,
Currency: 34,
Percent: 34,
Duration: 60,
Rating: 0,
},
{
Id: 7,
Number: 8754,
Decimal: 8754,
Currency: 8754,
Percent: 8754,
Duration: null,
Rating: 4,
},
{
Id: 8,
Number: 3234,
Decimal: 3234.547,
Currency: 3234.547,
Percent: 3234,
Duration: 70,
Rating: 5,
},
{
Id: 9,
Number: 44,
Decimal: 44.2647,
Currency: 44.2647,
Percent: 44,
Duration: 80,
Rating: 0,
},
{
Id: 10,
Number: 33,
Decimal: 33.98,
Currency: 33.98,
Percent: 33,
Duration: 90,
Rating: 1,
},
];
it.only('Number based- List & CRUD', async function () { it.only('Number based- List & CRUD', async function () {
// list 10 records // list 10 records
let rsp = await ncAxiosGet({ let rsp = await ncAxiosGet({
@ -1332,11 +1239,7 @@ function numberBased() {
isLastPage: false, isLastPage: false,
}; };
expect(rsp.body.pageInfo).to.deep.equal(pageInfo); expect(rsp.body.pageInfo).to.deep.equal(pageInfo);
if (isPg(context)) {
expect(rsp.body.list).to.deep.equal(recordsPg);
} else {
expect(rsp.body.list).to.deep.equal(records); expect(rsp.body.list).to.deep.equal(records);
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -1360,11 +1263,7 @@ function numberBased() {
rsp = await ncAxiosGet({ rsp = await ncAxiosGet({
url: `/api/v1/tables/${table.id}/rows/401`, url: `/api/v1/tables/${table.id}/rows/401`,
}); });
if (isPg(context)) {
expect(rsp.body).to.deep.equal({ ...recordsPg[0], Id: 401 });
} else {
expect(rsp.body).to.deep.equal({ ...records[0], Id: 401 }); expect(rsp.body).to.deep.equal({ ...records[0], Id: 401 });
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -1377,14 +1276,6 @@ function numberBased() {
Duration: 55, Duration: 55,
Rating: 5, Rating: 5,
}; };
const updatedRecordPg = {
Number: 55,
Decimal: 55.5,
Currency: 55.5,
Percent: 55,
Duration: 55,
Rating: 5,
};
const updatedRecords = [ const updatedRecords = [
{ {
@ -1404,25 +1295,6 @@ function numberBased() {
...updatedRecord, ...updatedRecord,
}, },
]; ];
const updatedRecordsPg = [
{
Id: 401,
...updatedRecordPg,
},
{
Id: 402,
...updatedRecordPg,
},
{
Id: 403,
...updatedRecordPg,
},
{
Id: 404,
...updatedRecordPg,
},
];
rsp = await ncAxiosPatch({ rsp = await ncAxiosPatch({
body: updatedRecords, body: updatedRecords,
}); });
@ -1437,11 +1309,8 @@ function numberBased() {
offset: 400, offset: 400,
}, },
}); });
if (isPg(context)) {
expect(rsp.body.list).to.deep.equal(updatedRecordsPg);
} else {
expect(rsp.body.list).to.deep.equal(updatedRecords); expect(rsp.body.list).to.deep.equal(updatedRecords);
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////

2
tests/playwright/pages/Dashboard/Grid/index.ts

@ -272,7 +272,7 @@ export class GridPage extends BasePage {
await this.get().locator('[data-testid="nc-check-all"]').nth(0).click({ await this.get().locator('[data-testid="nc-check-all"]').nth(0).click({
button: 'right', button: 'right',
}); });
await this.rootPage.locator('text=Update Selected Rows').click(); await this.rootPage.locator('.nc-menu-item:has-text("Update Selected Rows")').click();
await this.dashboard.waitForLoaderToDisappear(); await this.dashboard.waitForLoaderToDisappear();
} }

5
tests/playwright/pages/Dashboard/common/Topbar/Share.ts

@ -56,10 +56,7 @@ export class TopbarSharePage extends BasePage {
.locator('.ant-switch') .locator('.ant-switch')
.nth(0) .nth(0)
.click({ .click({
position: { position: { x: 4, y: 4 },
x: 4,
y: 4,
},
}); });
} }

Loading…
Cancel
Save