Browse Source

Merge branch 'develop' into feat/kanban-view

pull/3818/head
Wing-Kam Wong 2 years ago
parent
commit
af34366f51
  1. 9
      .all-contributorsrc
  2. 1
      README.md
  3. 10
      packages/nc-gui/components/cell/Checkbox.vue
  4. 38
      packages/nc-gui/components/cell/Url.vue
  5. 4
      packages/nc-gui/components/dashboard/TreeView.vue
  6. 4
      packages/nc-gui/components/dashboard/settings/Metadata.vue
  7. 2
      packages/nc-gui/components/dashboard/settings/Misc.vue
  8. 2
      packages/nc-gui/components/dlg/AirtableImport.vue
  9. 2
      packages/nc-gui/components/general/HelpAndSupport.vue
  10. 8
      packages/nc-gui/components/general/MiniSidebar.vue
  11. 10
      packages/nc-gui/components/general/Social.vue
  12. 16
      packages/nc-gui/components/general/SocialCard.vue
  13. 2
      packages/nc-gui/components/shared-view/Grid.vue
  14. 2
      packages/nc-gui/components/smartsheet-toolbar/AddRow.vue
  15. 4
      packages/nc-gui/components/smartsheet-toolbar/ColumnFilterMenu.vue
  16. 2
      packages/nc-gui/components/smartsheet-toolbar/Export.vue
  17. 4
      packages/nc-gui/components/smartsheet-toolbar/ExportSubActions.vue
  18. 4
      packages/nc-gui/components/smartsheet-toolbar/FieldsMenu.vue
  19. 12
      packages/nc-gui/components/smartsheet-toolbar/MoreActions.vue
  20. 7
      packages/nc-gui/components/smartsheet-toolbar/ShareView.vue
  21. 2
      packages/nc-gui/components/smartsheet-toolbar/SortListMenu.vue
  22. 16
      packages/nc-gui/components/smartsheet-toolbar/ViewActions.vue
  23. 4
      packages/nc-gui/components/smartsheet/ApiSnippet.vue
  24. 8
      packages/nc-gui/components/smartsheet/Form.vue
  25. 2
      packages/nc-gui/components/smartsheet/Gallery.vue
  26. 16
      packages/nc-gui/components/smartsheet/Grid.vue
  27. 2
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  28. 2
      packages/nc-gui/components/smartsheet/expanded-form/Header.vue
  29. 2
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  30. 2
      packages/nc-gui/components/smartsheet/sidebar/index.vue
  31. 2
      packages/nc-gui/components/smartsheet/sidebar/toolbar/ToggleDrawer.vue
  32. 2
      packages/nc-gui/components/tabs/Smartsheet.vue
  33. 6
      packages/nc-gui/components/tabs/auth/UserManagement.vue
  34. 13
      packages/nc-gui/components/virtual-cell/Lookup.vue
  35. 2
      packages/nc-gui/components/webhook/Drawer.vue
  36. 2
      packages/nc-gui/components/webhook/List.vue
  37. 48
      packages/nc-gui/composables/useCellUrlConfig.ts
  38. 6
      packages/nc-gui/layouts/base.vue
  39. 2
      packages/nc-gui/lib/types.ts
  40. 1
      packages/nc-gui/package.json
  41. 18
      packages/nc-gui/pages/[projectType]/[projectId]/index.vue
  42. 2
      packages/nc-gui/pages/[projectType]/[projectId]/index/index.vue
  43. 158
      packages/nc-gui/pages/[projectType]/form/[viewId]/index.vue
  44. 3
      packages/nc-gui/pages/[projectType]/view/[viewId].vue
  45. 2
      packages/nc-gui/pages/index/index/[projectId].vue
  46. 8
      packages/nc-gui/pages/index/index/index.vue
  47. 2
      packages/nc-gui/plugins/tele.ts
  48. 20
      packages/nc-gui/web-types.json

9
.all-contributorsrc

@ -864,6 +864,15 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "dolsem",
"name": "Denis Olsem",
"avatar_url": "https://avatars.githubusercontent.com/u/14323955?v=4",
"profile": "https://github.com/dolsem",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

1
README.md

@ -453,6 +453,7 @@ Our mission is to provide the most powerful no-code interface for databases whic
<td align="center"><a href="https://github.com/drsantam"><img src="https://avatars.githubusercontent.com/u/10681456?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Santam Chakraborty</b></sub></a><br /><a href="#translation-drsantam" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/drsantam"><img src="https://avatars.githubusercontent.com/u/10681456?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Santam Chakraborty</b></sub></a><br /><a href="#translation-drsantam" title="Translation">🌍</a></td>
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=eltociear" title="Code">💻</a></td> <td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=eltociear" title="Code">💻</a></td>
<td align="center"><a href="http://asheerrizvi.com"><img src="https://avatars.githubusercontent.com/u/17976252?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Asheer Rizvi</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=asheerrizvi" title="Code">💻</a></td> <td align="center"><a href="http://asheerrizvi.com"><img src="https://avatars.githubusercontent.com/u/17976252?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Asheer Rizvi</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=asheerrizvi" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/dolsem"><img src="https://avatars.githubusercontent.com/u/14323955?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Denis Olsem</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=dolsem" title="Code">💻</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

10
packages/nc-gui/components/cell/Checkbox.vue

@ -42,9 +42,15 @@ function onClick() {
<template> <template>
<div <div
class="flex" class="flex"
:class="{ 'justify-center': !isForm, 'nc-cell-hover-show': !vModel && !readOnly, 'opacity-0': readOnly && !vModel }" :class="{
'justify-center': !isForm,
'w-full': isForm,
'nc-cell-hover-show': !vModel && !readOnly,
'opacity-0': readOnly && !vModel,
}"
@click="onClick"
> >
<div class="px-1 pt-1 rounded-full items-center" :class="{ 'bg-gray-100': !vModel }" @click="onClick"> <div class="px-1 pt-1 rounded-full items-center" :class="{ 'bg-gray-100': !vModel }">
<component <component
:is="getMdiIcon(vModel ? checkboxMeta.icon.checked : checkboxMeta.icon.unchecked)" :is="getMdiIcon(vModel ? checkboxMeta.icon.checked : checkboxMeta.icon.unchecked)"
:style="{ :style="{

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

@ -1,22 +1,35 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n' import {
import { CellUrlDisableOverlayInj, ColumnInj, EditModeInj, computed, inject, isValidURL, ref } from '#imports' CellUrlDisableOverlayInj,
import MiCircleWarning from '~icons/mi/circle-warning' ColumnInj,
EditModeInj,
computed,
inject,
isValidURL,
ref,
useCellUrlConfig,
useI18n,
watch,
} from '#imports'
const { modelValue: value } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const { t } = useI18n()
interface Props { interface Props {
modelValue?: string | null modelValue?: string | null
} }
const { modelValue: value } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const { t } = useI18n()
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
const editEnabled = inject(EditModeInj)! const editEnabled = inject(EditModeInj)!
const disableOverlay = inject(CellUrlDisableOverlayInj) const disableOverlay = inject(CellUrlDisableOverlayInj)
// Used in the logic of when to display error since we are not storing the url if its not valid // Used in the logic of when to display error since we are not storing the url if its not valid
const localState = ref(value) const localState = ref(value)
@ -40,7 +53,8 @@ const url = computed(() => {
return `https://${value}` return `https://${value}`
}) })
const urlOptions = useCellUrlConfig(url)
const { cellUrlOptions } = useCellUrlConfig(url)
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
@ -62,20 +76,20 @@ watch(
<input v-if="editEnabled" :ref="focus" v-model="vModel" class="outline-none text-sm w-full" @blur="editEnabled = false" /> <input v-if="editEnabled" :ref="focus" v-model="vModel" class="outline-none text-sm w-full" @blur="editEnabled = false" />
<nuxt-link <nuxt-link
v-else-if="isValid && !urlOptions?.overlay" v-else-if="isValid && !cellUrlOptions?.overlay"
class="z-3 text-sm underline hover:opacity-75" class="z-3 text-sm underline hover:opacity-75"
:to="url" :to="url"
:target="urlOptions?.behavior === 'replace' ? undefined : '_blank'" :target="cellUrlOptions?.behavior === 'replace' ? undefined : '_blank'"
> >
{{ value }} {{ value }}
</nuxt-link> </nuxt-link>
<nuxt-link <nuxt-link
v-else-if="isValid && !disableOverlay && urlOptions?.overlay" v-else-if="isValid && !disableOverlay && cellUrlOptions?.overlay"
class="z-3 w-full h-full text-center !no-underline hover:opacity-75" class="z-3 w-full h-full text-center !no-underline hover:opacity-75"
:to="url" :to="url"
:target="urlOptions?.behavior === 'replace' ? undefined : '_blank'" :target="cellUrlOptions?.behavior === 'replace' ? undefined : '_blank'"
> >
{{ urlOptions.overlay }} {{ cellUrlOptions.overlay }}
</nuxt-link> </nuxt-link>
<span v-else class="w-9/10 overflow-ellipsis overflow-hidden">{{ value }}</span> <span v-else class="w-9/10 overflow-ellipsis overflow-hidden">{{ value }}</span>

4
packages/nc-gui/components/dashboard/TreeView.vue

@ -286,7 +286,7 @@ function openTableCreateDialog() {
<a-menu-item v-if="isUIAllowed('importRequest')" key="add-new-table" class="py-1 rounded-b"> <a-menu-item v-if="isUIAllowed('importRequest')" key="add-new-table" class="py-1 rounded-b">
<a <a
v-t="['e:datasource:import-request']" v-e="['e:datasource:import-request']"
href="https://github.com/nocodb/nocodb/issues/2052" href="https://github.com/nocodb/nocodb/issues/2052"
target="_blank" target="_blank"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)" class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)"
@ -306,7 +306,7 @@ function openTableCreateDialog() {
<div <div
v-for="table of tables" v-for="table of tables"
:key="table.id" :key="table.id"
v-t="['a:table:open']" v-e="['a:table:open']"
:class="[ :class="[
{ hidden: !filteredTables?.includes(table), active: activeTable === table.title }, { hidden: !filteredTables?.includes(table), active: activeTable === table.title },
`nc-project-tree-tbl nc-project-tree-tbl-${table.title}`, `nc-project-tree-tbl nc-project-tree-tbl-${table.title}`,

4
packages/nc-gui/components/dashboard/settings/Metadata.vue

@ -83,7 +83,7 @@ const columns = [
<div class="flex flex-col w-3/5"> <div class="flex flex-col w-3/5">
<div class="flex flex-row justify-end items-center w-full mb-4"> <div class="flex flex-row justify-end items-center w-full mb-4">
<!-- Reload --> <!-- Reload -->
<a-button v-t="['a:proj-meta:meta-data:reload']" class="self-start nc-btn-metasync-reload" @click="loadMetaDiff"> <a-button v-e="['a:proj-meta:meta-data:reload']" class="self-start nc-btn-metasync-reload" @click="loadMetaDiff">
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiReload :class="{ 'animate-infinite animate-spin !text-success': isLoading }" /> <MdiReload :class="{ 'animate-infinite animate-spin !text-success': isLoading }" />
{{ $t('general.reload') }} {{ $t('general.reload') }}
@ -112,7 +112,7 @@ const columns = [
<div class="flex place-content-center w-2/5"> <div class="flex place-content-center w-2/5">
<!-- Sync Now --> <!-- Sync Now -->
<div v-if="isDifferent"> <div v-if="isDifferent">
<a-button v-t="['a:proj-meta:meta-data:sync']" class="nc-btn-metasync-sync-now" type="primary" @click="syncMetaDiff"> <a-button v-e="['a:proj-meta:meta-data:sync']" class="nc-btn-metasync-sync-now" type="primary" @click="syncMetaDiff">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<MdiDatabaseSync /> <MdiDatabaseSync />
{{ $t('activity.metaSync') }} {{ $t('activity.metaSync') }}

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

@ -10,7 +10,7 @@ watch(includeM2M, async () => await loadTables())
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<div class="flex flex-row items-center w-full mb-4 gap-2"> <div class="flex flex-row items-center w-full mb-4 gap-2">
<!-- Show M2M Tables --> <!-- Show M2M Tables -->
<a-checkbox v-model:checked="includeM2M" v-t="['c:themes:show-m2m-tables']">{{ <a-checkbox v-model:checked="includeM2M" v-e="['c:themes:show-m2m-tables']">{{
$t('msg.info.showM2mTables') $t('msg.info.showM2mTables')
}}</a-checkbox> }}</a-checkbox>
</div> </div>

2
packages/nc-gui/components/dlg/AirtableImport.vue

@ -391,7 +391,7 @@ onBeforeUnmount(() => {
<!-- Import --> <!-- Import -->
<a-button <a-button
key="submit" key="submit"
v-t="['c:sync-airtable:save-and-sync']" v-e="['c:sync-airtable:save-and-sync']"
type="primary" type="primary"
class="nc-btn-airtable-import" class="nc-btn-airtable-import"
:disabled="disableImportButton" :disabled="disableImportButton"

2
packages/nc-gui/components/general/HelpAndSupport.vue

@ -41,7 +41,7 @@ const openSwaggerLink = () => {
<template #before> <template #before>
<a-list-item v-if="project"> <a-list-item v-if="project">
<nuxt-link <nuxt-link
v-t="['a:navbar:user:swagger']" v-e="['a:navbar:user:swagger']"
class="!no-underline !text-current py-4 font-semibold" class="!no-underline !text-current py-4 font-semibold"
target="_blank" target="_blank"
@click="openSwaggerLink" @click="openSwaggerLink"

8
packages/nc-gui/components/general/MiniSidebar.vue

@ -37,7 +37,7 @@ const logout = () => {
<a-menu class="ml-2 !py-0 min-w-32 leading-8 !rounded nc-menu-account"> <a-menu class="ml-2 !py-0 min-w-32 leading-8 !rounded nc-menu-account">
<a-menu-item-group title="User Settings"> <a-menu-item-group title="User Settings">
<a-menu-item key="email" class="!rounded-t"> <a-menu-item key="email" class="!rounded-t">
<nuxt-link v-t="['c:navbar:user:email']" class="group flex items-center no-underline py-2" to="/user"> <nuxt-link v-e="['c:navbar:user:email']" class="group flex items-center no-underline py-2" to="/user">
<MdiAt class="mt-1 group-hover:text-success" /> <MdiAt class="mt-1 group-hover:text-success" />
&nbsp; &nbsp;
<span class="prose group-hover:text-black nc-user-menu-email">{{ email }}</span> <span class="prose group-hover:text-black nc-user-menu-email">{{ email }}</span>
@ -47,7 +47,7 @@ const logout = () => {
<a-menu-divider class="!m-0" /> <a-menu-divider class="!m-0" />
<a-menu-item key="signout" class="!rounded-b"> <a-menu-item key="signout" class="!rounded-b">
<div v-t="['a:navbar:user:sign-out']" class="group flex items-center py-2" @click="logout"> <div v-e="['a:navbar:user:sign-out']" class="group flex items-center py-2" @click="logout">
<MdiLogout class="group-hover:(!text-red-500)" />&nbsp; <MdiLogout class="group-hover:(!text-red-500)" />&nbsp;
<span class="prose font-semibold text-gray-500 group-hover:text-black nc-user-menu-signout"> <span class="prose font-semibold text-gray-500 group-hover:text-black nc-user-menu-signout">
{{ $t('general.signOut') }} {{ $t('general.signOut') }}
@ -76,7 +76,7 @@ const logout = () => {
<a-menu-item class="active:(ring ring-accent)"> <a-menu-item class="active:(ring ring-accent)">
<div <div
v-t="['c:project:create:xcdb']" v-e="['c:project:create:xcdb']"
class="group flex items-center gap-2 py-2 hover:text-primary" class="group flex items-center gap-2 py-2 hover:text-primary"
@click="navigateTo('/project/create')" @click="navigateTo('/project/create')"
> >
@ -87,7 +87,7 @@ const logout = () => {
<a-menu-item class="rounded-b active:(ring ring-accent)"> <a-menu-item class="rounded-b active:(ring ring-accent)">
<div <div
v-t="['c:project:create:extdb']" v-e="['c:project:create:extdb']"
class="group flex items-center gap-2 py-2 hover:text-primary" class="group flex items-center gap-2 py-2 hover:text-primary"
@click="navigateTo('/project/create-external')" @click="navigateTo('/project/create-external')"
> >

10
packages/nc-gui/components/general/Social.vue

@ -23,18 +23,18 @@ const isZhLang = $computed(() => locale.value.startsWith('zh'))
/> />
<div v-else class="flex justify-between gap-1 w-full px-2"> <div v-else class="flex justify-between gap-1 w-full px-2">
<MdiDiscord v-t="['e:community:discord']" class="icon text-[#7289DA]" @click="open('https://discord.gg/5RgZmkW')" /> <MdiDiscord v-e="['e:community:discord']" class="icon text-[#7289DA]" @click="open('https://discord.gg/5RgZmkW')" />
<div <div
v-t="['e:community:discourse']" v-e="['e:community:discourse']"
class="icon flex items-center justify-center min-w-[43px]" class="icon flex items-center justify-center min-w-[43px]"
@click="open('https://community.nocodb.com/')" @click="open('https://community.nocodb.com/')"
> >
<div class="discourse" /> <div class="discourse" />
</div> </div>
<MdiReddit v-t="['e:community:reddit']" class="icon text-[#FF4600]" @click="open('https://www.reddit.com/r/NocoDB/')" /> <MdiReddit v-e="['e:community:reddit']" class="icon text-[#FF4600]" @click="open('https://www.reddit.com/r/NocoDB/')" />
<MdiTwitter v-t="['e:community:twitter']" class="icon text-[#1DA1F2]" @click="open('https://twitter.com/NocoDB')" /> <MdiTwitter v-e="['e:community:twitter']" class="icon text-[#1DA1F2]" @click="open('https://twitter.com/NocoDB')" />
<MdiCalendarMonth <MdiCalendarMonth
v-t="['e:community:book-demo']" v-e="['e:community:book-demo']"
class="icon text-green-500" class="icon text-green-500"
@click="open('https://calendly.com/nocodb-meeting')" @click="open('https://calendly.com/nocodb-meeting')"
/> />

16
packages/nc-gui/components/general/SocialCard.vue

@ -13,7 +13,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
<a-list-item> <a-list-item>
<nuxt-link <nuxt-link
v-t="['e:docs']" v-e="['e:docs']"
class="text-primary !no-underline !text-current" class="text-primary !no-underline !text-current"
target="_blank" target="_blank"
to="https://docs.nocodb.com/" to="https://docs.nocodb.com/"
@ -26,7 +26,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item> </a-list-item>
<a-list-item> <a-list-item>
<nuxt-link <nuxt-link
v-t="['e:api-docs']" v-e="['e:api-docs']"
class="text-primary !no-underline !text-current" class="text-primary !no-underline !text-current"
target="_blank" target="_blank"
to="https://apis.nocodb.com/" to="https://apis.nocodb.com/"
@ -40,7 +40,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item> </a-list-item>
<a-list-item> <a-list-item>
<nuxt-link <nuxt-link
v-t="['e:community:github']" v-e="['e:community:github']"
class="text-primary !no-underline !text-current" class="text-primary !no-underline !text-current"
to="https://github.com/nocodb/nocodb" to="https://github.com/nocodb/nocodb"
target="_blank" target="_blank"
@ -66,7 +66,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item> </a-list-item>
<a-list-item> <a-list-item>
<nuxt-link <nuxt-link
v-t="['e:community:book-demo']" v-e="['e:community:book-demo']"
class="!no-underline !text-current" class="!no-underline !text-current"
to="https://calendly.com/nocodb-meeting" to="https://calendly.com/nocodb-meeting"
target="_blank" target="_blank"
@ -82,7 +82,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item> </a-list-item>
<a-list-item> <a-list-item>
<nuxt-link <nuxt-link
v-t="['e:community:discord']" v-e="['e:community:discord']"
class="!no-underline !text-current" class="!no-underline !text-current"
to="https://discord.gg/5RgZmkW" to="https://discord.gg/5RgZmkW"
target="_blank" target="_blank"
@ -98,7 +98,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item> </a-list-item>
<a-list-item> <a-list-item>
<nuxt-link <nuxt-link
v-t="['e:community:twitter']" v-e="['e:community:twitter']"
class="!no-underline !text-current" class="!no-underline !text-current"
to="https://twitter.com/NocoDB" to="https://twitter.com/NocoDB"
target="_blank" target="_blank"
@ -113,7 +113,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</nuxt-link> </nuxt-link>
</a-list-item> </a-list-item>
<a-list-item> <a-list-item>
<nuxt-link v-t="['e:hiring']" class="!no-underline !text-current" target="_blank" to="http://careers.nocodb.com"> <nuxt-link v-e="['e:hiring']" class="!no-underline !text-current" target="_blank" to="http://careers.nocodb.com">
<div class="flex items-center text-sm"> <div class="flex items-center text-sm">
<!-- todo: i18n --> <!-- todo: i18n -->
<div class="ml-3"> <div class="ml-3">
@ -124,7 +124,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item> </a-list-item>
<a-list-item> <a-list-item>
<nuxt-link <nuxt-link
v-t="['e:community:reddit']" v-e="['e:community:reddit']"
class="!no-underline !text-current" class="!no-underline !text-current"
target="_blank" target="_blank"
to="https://www.reddit.com/r/NocoDB/" to="https://www.reddit.com/r/NocoDB/"

2
packages/nc-gui/components/shared-view/Grid.vue

@ -2,7 +2,6 @@
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { ActiveViewInj, FieldsInj, IsPublicInj, MetaInj, ReadonlyInj, ReloadViewDataHookInj } from '~/context' import { ActiveViewInj, FieldsInj, IsPublicInj, MetaInj, ReadonlyInj, ReloadViewDataHookInj } from '~/context'
const { sharedView, meta, sorts, nestedFilters } = useSharedView() const { sharedView, meta, sorts, nestedFilters } = useSharedView()
@ -10,6 +9,7 @@ const { signedIn } = useGlobal()
const { loadProject } = useProject(meta?.value.project_id) const { loadProject } = useProject(meta?.value.project_id)
const reloadEventHook = createEventHook<void>() const reloadEventHook = createEventHook<void>()
provide(ReloadViewDataHookInj, reloadEventHook) provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, true) provide(ReadonlyInj, true)
provide(MetaInj, meta) provide(MetaInj, meta)

2
packages/nc-gui/components/smartsheet-toolbar/AddRow.vue

@ -14,7 +14,7 @@ const onClick = () => {
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title> {{ $t('activity.addRow') }} </template> <template #title> {{ $t('activity.addRow') }} </template>
<div <div
v-t="['c:row:add:grid-top']" v-e="['c:row:add:grid-top']"
:class="{ 'group': !isLocked, 'disabled-ring': isLocked }" :class="{ 'group': !isLocked, 'disabled-ring': isLocked }"
class="nc-add-new-row-btn flex align-center" class="nc-add-new-row-btn flex align-center"
> >

4
packages/nc-gui/components/smartsheet-toolbar/ColumnFilterMenu.vue

@ -53,7 +53,7 @@ const filterAutoSaveLoc = computed({
<template> <template>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-filter-menu"> <a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-filter-menu">
<div :class="{ 'nc-badge nc-active-btn': filtersLength }"> <div :class="{ 'nc-badge nc-active-btn': filtersLength }">
<a-button v-t="['c:filter']" class="nc-filter-menu-btn nc-toolbar-btn txt-sm" :disabled="isLocked"> <a-button v-e="['c:filter']" class="nc-filter-menu-btn nc-toolbar-btn txt-sm" :disabled="isLocked">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiFilterOutline /> <MdiFilterOutline />
<!-- Filter --> <!-- Filter -->
@ -80,7 +80,7 @@ const filterAutoSaveLoc = computed({
<div class="flex-1" /> <div class="flex-1" />
<a-button <a-button
v-show="!filterAutoSave" v-show="!filterAutoSave"
v-t="['a:filter:auto-apply']" v-e="['a:filter:auto-apply']"
size="small" size="small"
class="text-xs ml-2" class="text-xs ml-2"
@click="applyChanges" @click="applyChanges"

2
packages/nc-gui/components/smartsheet-toolbar/Export.vue

@ -1,6 +1,6 @@
<template> <template>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu"> <a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu">
<a-button v-t="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn"> <a-button v-e="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn">
<div class="flex gap-2 items-center"> <div class="flex gap-2 items-center">
<MdiDownload class="group-hover:text-accent text-gray-500" /> <MdiDownload class="group-hover:text-accent text-gray-500" />
<span class="text-capitalize !text-sm font-weight-normal">Download</span> <span class="text-capitalize !text-sm font-weight-normal">Download</span>

4
packages/nc-gui/components/smartsheet-toolbar/ExportSubActions.vue

@ -75,14 +75,14 @@ const exportFile = async (exportType: ExportTypes) => {
<template> <template>
<a-menu-item> <a-menu-item>
<div v-t="['a:actions:download-csv']" class="nc-project-menu-item" @click="exportFile(ExportTypes.CSV)"> <div v-e="['a:actions:download-csv']" class="nc-project-menu-item" @click="exportFile(ExportTypes.CSV)">
<MdiDownloadOutline class="text-gray-500" /> <MdiDownloadOutline class="text-gray-500" />
<!-- Download as CSV --> <!-- Download as CSV -->
{{ $t('activity.downloadCSV') }} {{ $t('activity.downloadCSV') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item> <a-menu-item>
<div v-t="['a:actions:download-excel']" class="nc-project-menu-item" @click="exportFile(ExportTypes.EXCEL)"> <div v-e="['a:actions:download-excel']" class="nc-project-menu-item" @click="exportFile(ExportTypes.EXCEL)">
<MdiDownloadOutline class="text-gray-500" /> <MdiDownloadOutline class="text-gray-500" />
<!-- Download as XLSX --> <!-- Download as XLSX -->
{{ $t('activity.downloadExcel') }} {{ $t('activity.downloadExcel') }}

4
packages/nc-gui/components/smartsheet-toolbar/FieldsMenu.vue

@ -118,7 +118,7 @@ const getIcon = (c: ColumnType) =>
<template> <template>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-fields-menu"> <a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-fields-menu">
<div :class="{ 'nc-badge nc-active-btn': isAnyFieldHidden }"> <div :class="{ 'nc-badge nc-active-btn': isAnyFieldHidden }">
<a-button v-t="['c:fields']" class="nc-fields-menu-btn nc-toolbar-btn" :disabled="isLocked"> <a-button v-e="['c:fields']" class="nc-fields-menu-btn nc-toolbar-btn" :disabled="isLocked">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiEyeOffOutline /> <MdiEyeOffOutline />
@ -146,7 +146,7 @@ const getIcon = (c: ColumnType) =>
<div v-show="filteredFieldList.includes(field)" :key="field.id" class="px-2 py-1 flex items-center" @click.stop> <div v-show="filteredFieldList.includes(field)" :key="field.id" class="px-2 py-1 flex items-center" @click.stop>
<a-checkbox <a-checkbox
v-model:checked="field.show" v-model:checked="field.show"
v-t="['a:fields:show-hide']" v-e="['a:fields:show-hide']"
class="shrink" class="shrink"
@change="saveOrUpdate(field, index)" @change="saveOrUpdate(field, index)"
> >

12
packages/nc-gui/components/smartsheet-toolbar/MoreActions.vue

@ -100,7 +100,7 @@ const exportFile = async (exportType: ExportTypes) => {
<template> <template>
<div> <div>
<a-dropdown> <a-dropdown>
<a-button v-t="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn"> <a-button v-e="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn">
<div class="flex gap-1 items-center"> <div class="flex gap-1 items-center">
<MdiFlashOutline /> <MdiFlashOutline />
@ -114,13 +114,13 @@ const exportFile = async (exportType: ExportTypes) => {
<template #overlay> <template #overlay>
<div class="bg-gray-50 py-2 shadow-lg !border"> <div class="bg-gray-50 py-2 shadow-lg !border">
<div> <div>
<div v-t="['a:actions:download-csv']" class="nc-menu-item" @click="exportFile(ExportTypes.CSV)"> <div v-e="['a:actions:download-csv']" class="nc-menu-item" @click="exportFile(ExportTypes.CSV)">
<MdiDownloadOutline class="text-gray-500" /> <MdiDownloadOutline class="text-gray-500" />
<!-- Download as CSV --> <!-- Download as CSV -->
{{ $t('activity.downloadCSV') }} {{ $t('activity.downloadCSV') }}
</div> </div>
<div v-t="['a:actions:download-excel']" class="nc-menu-item" @click="exportFile(ExportTypes.EXCEL)"> <div v-e="['a:actions:download-excel']" class="nc-menu-item" @click="exportFile(ExportTypes.EXCEL)">
<MdiDownloadOutline class="text-gray-500" /> <MdiDownloadOutline class="text-gray-500" />
<!-- Download as XLSX --> <!-- Download as XLSX -->
{{ $t('activity.downloadExcel') }} {{ $t('activity.downloadExcel') }}
@ -128,7 +128,7 @@ const exportFile = async (exportType: ExportTypes) => {
<div <div
v-if="isUIAllowed('csvImport') && !isView && !isPublicView" v-if="isUIAllowed('csvImport') && !isView && !isPublicView"
v-t="['a:actions:upload-csv']" v-e="['a:actions:upload-csv']"
class="nc-menu-item" class="nc-menu-item"
:class="{ disabled: isLocked }" :class="{ disabled: isLocked }"
@click="!isLocked ? (quickImportDialog = true) : {}" @click="!isLocked ? (quickImportDialog = true) : {}"
@ -140,7 +140,7 @@ const exportFile = async (exportType: ExportTypes) => {
<div <div
v-if="isUIAllowed('sharedViewList') && !isView && !isPublicView" v-if="isUIAllowed('sharedViewList') && !isView && !isPublicView"
v-t="['a:actions:shared-view-list']" v-e="['a:actions:shared-view-list']"
class="nc-menu-item" class="nc-menu-item"
@click="sharedViewListDlg = true" @click="sharedViewListDlg = true"
> >
@ -150,7 +150,7 @@ const exportFile = async (exportType: ExportTypes) => {
</div> </div>
<div <div
v-if="isUIAllowed('webhook') && !isView && !isPublicView" v-if="isUIAllowed('webhook') && !isView && !isPublicView"
v-t="['c:actions:webhook']" v-e="['c:actions:webhook']"
class="nc-menu-item" class="nc-menu-item"
@click="showWebhookDrawer = true" @click="showWebhookDrawer = true"
> >

7
packages/nc-gui/components/smartsheet-toolbar/ShareView.vue

@ -114,7 +114,7 @@ watch(passwordProtected, (value) => {
<div> <div>
<a-button <a-button
v-if="isUIAllowed('share-view') && !isSharedBase" v-if="isUIAllowed('share-view') && !isSharedBase"
v-t="['c:view:share']" v-e="['c:view:share']"
outlined outlined
class="nc-btn-share-view nc-toolbar-btn" class="nc-btn-share-view nc-toolbar-btn"
> >
@ -136,11 +136,10 @@ watch(passwordProtected, (value) => {
> >
<div class="share-link-box nc-share-link-box bg-primary-50"> <div class="share-link-box nc-share-link-box bg-primary-50">
<div class="flex-1 h-min text-xs">{{ sharedViewUrl }}</div> <div class="flex-1 h-min text-xs">{{ sharedViewUrl }}</div>
<!-- <v-spacer /> --> <a v-e="['c:view:share:open-url']" :href="sharedViewUrl" target="_blank">
<a v-t="['c:view:share:open-url']" :href="sharedViewUrl" target="_blank">
<MdiOpenInNewIcon class="text-sm text-gray-500 mt-2" /> <MdiOpenInNewIcon class="text-sm text-gray-500 mt-2" />
</a> </a>
<MdiCopyIcon v-t="['c:view:share:copy-url']" class="text-gray-500 text-sm cursor-pointer" @click="copyLink" /> <MdiCopyIcon v-e="['c:view:share:copy-url']" class="text-gray-500 text-sm cursor-pointer" @click="copyLink" />
</div> </div>
<a-collapse ghost> <a-collapse ghost>

2
packages/nc-gui/components/smartsheet-toolbar/SortListMenu.vue

@ -43,7 +43,7 @@ watch(
<template> <template>
<a-dropdown offset-y class="" :trigger="['click']" overlay-class-name="nc-dropdown-sort-menu"> <a-dropdown offset-y class="" :trigger="['click']" overlay-class-name="nc-dropdown-sort-menu">
<div :class="{ 'nc-badge nc-active-btn': sorts?.length }"> <div :class="{ 'nc-badge nc-active-btn': sorts?.length }">
<a-button v-t="['c:sort']" class="nc-sort-menu-btn nc-toolbar-btn" :disabled="isLocked" <a-button v-e="['c:sort']" class="nc-sort-menu-btn nc-toolbar-btn" :disabled="isLocked"
><div class="flex items-center gap-1"> ><div class="flex items-center gap-1">
<MdiSort /> <MdiSort />
<!-- Sort --> <!-- Sort -->

16
packages/nc-gui/components/smartsheet-toolbar/ViewActions.vue

@ -82,7 +82,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<template> <template>
<div> <div>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu"> <a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu">
<a-button v-t="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn"> <a-button v-e="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn">
<div class="flex gap-2 items-center"> <div class="flex gap-2 items-center">
<component <component
:is="viewIcons[selectedView?.type].icon" :is="viewIcons[selectedView?.type].icon"
@ -106,7 +106,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
class="scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !py-0" class="scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !py-0"
> >
<template #title> <template #title>
<div v-t="['c:navdraw:preview-as']" class="nc-project-menu-item group px-0 !py-0"> <div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group px-0 !py-0">
<SmartsheetToolbarLockType hide-tick :type="selectedView?.lock_type || LockType.Collaborative" /> <SmartsheetToolbarLockType hide-tick :type="selectedView?.lock_type || LockType.Collaborative" />
<MaterialSymbolsChevronRightRounded <MaterialSymbolsChevronRightRounded
@ -130,7 +130,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-sub-menu key="download"> <a-sub-menu key="download">
<template #title> <template #title>
<!-- Download --> <!-- Download -->
<div v-t="['c:navdraw:preview-as']" class="nc-project-menu-item group"> <div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group">
<MdiDownload class="group-hover:text-accent text-gray-500" /> <MdiDownload class="group-hover:text-accent text-gray-500" />
{{ $t('general.download') }} {{ $t('general.download') }}
<div class="flex-1" /> <div class="flex-1" />
@ -148,7 +148,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-sub-menu key="upload"> <a-sub-menu key="upload">
<!-- Upload --> <!-- Upload -->
<template #title> <template #title>
<div v-t="['c:navdraw:preview-as']" class="nc-project-menu-item group"> <div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group">
<MdiUpload class="group-hover:text-accent text-gray-500" /> <MdiUpload class="group-hover:text-accent text-gray-500" />
{{ $t('general.upload') }} {{ $t('general.upload') }}
<div class="flex-1" /> <div class="flex-1" />
@ -163,7 +163,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-menu-item> <a-menu-item>
<div <div
v-if="isUIAllowed('csvImport') && !isView && !isPublicView" v-if="isUIAllowed('csvImport') && !isView && !isPublicView"
v-t="['a:actions:upload-csv']" v-e="['a:actions:upload-csv']"
class="nc-project-menu-item" class="nc-project-menu-item"
:class="{ disabled: isLocked }" :class="{ disabled: isLocked }"
@click="!isLocked ? (quickImportDialog = true) : {}" @click="!isLocked ? (quickImportDialog = true) : {}"
@ -180,7 +180,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-menu-item> <a-menu-item>
<div <div
v-if="isUIAllowed('SharedViewList') && !isView && !isPublicView" v-if="isUIAllowed('SharedViewList') && !isView && !isPublicView"
v-t="['a:actions:shared-view-list']" v-e="['a:actions:shared-view-list']"
class="py-2 flex gap-2 items-center" class="py-2 flex gap-2 items-center"
@click="sharedViewListDlg = true" @click="sharedViewListDlg = true"
> >
@ -192,7 +192,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-menu-item v-if="!isSqlView"> <a-menu-item v-if="!isSqlView">
<div <div
v-if="isUIAllowed('webhook') && !isView && !isPublicView" v-if="isUIAllowed('webhook') && !isView && !isPublicView"
v-t="['c:actions:webhook']" v-e="['c:actions:webhook']"
class="py-2 flex gap-2 items-center" class="py-2 flex gap-2 items-center"
@click="showWebhookDrawer = true" @click="showWebhookDrawer = true"
> >
@ -203,7 +203,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-menu-item> <a-menu-item>
<div <div
v-if="!isSharedBase && !isPublicView" v-if="!isSharedBase && !isPublicView"
v-t="['c:snippet:open']" v-e="['c:snippet:open']"
class="py-2 flex gap-2 items-center" class="py-2 flex gap-2 items-center"
@click="showApiSnippetDrawer = true" @click="showApiSnippetDrawer = true"
> >

4
packages/nc-gui/components/smartsheet/ApiSnippet.vue

@ -169,7 +169,7 @@ watch($$(activeLang), (newLang) => {
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-button <a-button
v-t="[ v-e="[
'c:snippet:copy', 'c:snippet:copy',
{ client: activeLang?.clients && (selectedClient || activeLang?.clients[0]), lang: activeLang?.name }, { client: activeLang?.clients && (selectedClient || activeLang?.clients[0]), lang: activeLang?.name },
]" ]"
@ -181,7 +181,7 @@ watch($$(activeLang), (newLang) => {
<div class="absolute bottom-4 flex flex-row justify-center w-[95%]"> <div class="absolute bottom-4 flex flex-row justify-center w-[95%]">
<a <a
v-t="['e:hiring']" v-e="['e:hiring']"
class="px-4 py-2 ! rounded shadow" class="px-4 py-2 ! rounded shadow"
href="https://angel.co/company/nocodb" href="https://angel.co/company/nocodb"
target="_blank" target="_blank"

8
packages/nc-gui/components/smartsheet/Form.vue

@ -591,7 +591,7 @@ onMounted(async () => {
<a-switch <a-switch
v-model:checked="element.required" v-model:checked="element.required"
v-t="['a:form-view:field:mark-required']" v-e="['a:form-view:field:mark-required']"
size="small" size="small"
class="ml-2" class="ml-2"
@change="updateColMeta(element)" @change="updateColMeta(element)"
@ -715,7 +715,7 @@ onMounted(async () => {
<!-- Show "Submit Another Form" button --> <!-- Show "Submit Another Form" button -->
<a-switch <a-switch
v-model:checked="formViewData.submit_another_form" v-model:checked="formViewData.submit_another_form"
v-t="[`a:form-view:submit-another-form`]" v-e="[`a:form-view:submit-another-form`]"
size="small" size="small"
class="nc-form-checkbox-submit-another-form" class="nc-form-checkbox-submit-another-form"
@change="updateView" @change="updateView"
@ -727,7 +727,7 @@ onMounted(async () => {
<!-- Show a blank form after 5 seconds --> <!-- Show a blank form after 5 seconds -->
<a-switch <a-switch
v-model:checked="formViewData.show_blank_form" v-model:checked="formViewData.show_blank_form"
v-t="[`a:form-view:show-blank-form`]" v-e="[`a:form-view:show-blank-form`]"
size="small" size="small"
class="nc-form-checkbox-show-blank-form" class="nc-form-checkbox-show-blank-form"
@change="updateView" @change="updateView"
@ -738,7 +738,7 @@ onMounted(async () => {
<div class="my-4"> <div class="my-4">
<a-switch <a-switch
v-model:checked="emailMe" v-model:checked="emailMe"
v-t="[`a:form-view:email-me`]" v-e="[`a:form-view:email-me`]"
size="small" size="small"
class="nc-form-checkbox-send-email" class="nc-form-checkbox-send-email"
@change="onEmailChange" @change="onEmailChange"

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

@ -144,7 +144,7 @@ openNewRecordFormHook?.on(async () => {
<img <img
v-for="(attachment, index) in attachments(record)" v-for="(attachment, index) in attachments(record)"
:key="`carousel-${record.row.id}-${index}`" :key="`carousel-${record.row.id}-${index}`"
class="h-52" class="h-52 object-cover"
:src="attachment.url" :src="attachment.url"
/> />
</a-carousel> </a-carousel>

16
packages/nc-gui/components/smartsheet/Grid.vue

@ -2,7 +2,6 @@
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk' import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
import { import {
ActiveViewInj, ActiveViewInj,
CellUrlDisableOverlayInj, CellUrlDisableOverlayInj,
@ -27,6 +26,7 @@ import {
ref, ref,
useEventListener, useEventListener,
useGridViewColumnWidth, useGridViewColumnWidth,
useI18n,
useSmartsheetStoreOrThrow, useSmartsheetStoreOrThrow,
useUIPermission, useUIPermission,
useViewData, useViewData,
@ -425,7 +425,7 @@ onBeforeUnmount(async () => {
</th> </th>
<th <th
v-if="!readOnly && !isLocked && isUIAllowed('add-column') && !isSqlView" v-if="!readOnly && !isLocked && isUIAllowed('add-column') && !isSqlView"
v-t="['c:column:add']" v-e="['c:column:add']"
class="cursor-pointer" class="cursor-pointer"
@click.stop="addColumnDropdown = true" @click.stop="addColumnDropdown = true"
> >
@ -486,7 +486,7 @@ onBeforeUnmount(async () => {
class="cursor-pointer flex items-center border-1 active:ring rounded p-1 hover:(bg-primary bg-opacity-10)" class="cursor-pointer flex items-center border-1 active:ring rounded p-1 hover:(bg-primary bg-opacity-10)"
> >
<MdiArrowExpand <MdiArrowExpand
v-t="['c:row-expand']" v-e="['c:row-expand']"
class="select-none transform hover:(text-accent scale-120) nc-row-expand" class="select-none transform hover:(text-accent scale-120) nc-row-expand"
@click="expandForm(row, state)" @click="expandForm(row, state)"
/> />
@ -544,7 +544,7 @@ onBeforeUnmount(async () => {
<tr v-if="!isView && !isLocked && isUIAllowed('xcDatatableEditable') && !isSqlView"> <tr v-if="!isView && !isLocked && isUIAllowed('xcDatatableEditable') && !isSqlView">
<td <td
v-t="['c:row:add:grid-bottom']" v-e="['c:row:add:grid-bottom']"
:colspan="visibleColLength + 1" :colspan="visibleColLength + 1"
class="text-left pointer nc-grid-add-new-cell cursor-pointer" class="text-left pointer nc-grid-add-new-cell cursor-pointer"
@click="addEmptyRow()" @click="addEmptyRow()"
@ -564,14 +564,14 @@ onBeforeUnmount(async () => {
<template v-if="!isLocked && isUIAllowed('xcDatatableEditable')" #overlay> <template v-if="!isLocked && isUIAllowed('xcDatatableEditable')" #overlay>
<a-menu class="shadow !rounded !py-0" @click="contextMenu = false"> <a-menu class="shadow !rounded !py-0" @click="contextMenu = false">
<a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget.row)"> <a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget.row)">
<div v-t="['a:row:delete']" class="nc-project-menu-item"> <div v-e="['a:row:delete']" class="nc-project-menu-item">
<!-- Delete Row --> <!-- Delete Row -->
{{ $t('activity.deleteRow') }} {{ $t('activity.deleteRow') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item @click="deleteSelectedRows"> <a-menu-item @click="deleteSelectedRows">
<div v-t="['a:row:delete-bulk']" class="nc-project-menu-item"> <div v-e="['a:row:delete-bulk']" class="nc-project-menu-item">
<!-- Delete Selected Rows --> <!-- Delete Selected Rows -->
{{ $t('activity.deleteSelectedRow') }} {{ $t('activity.deleteSelectedRow') }}
</div> </div>
@ -579,11 +579,11 @@ onBeforeUnmount(async () => {
<!-- Clear cell --> <!-- Clear cell -->
<a-menu-item v-if="contextMenuTarget" @click="clearCell(contextMenuTarget)"> <a-menu-item v-if="contextMenuTarget" @click="clearCell(contextMenuTarget)">
<div v-t="['a:row:clear']" class="nc-project-menu-item">{{ $t('activity.clearCell') }}</div> <div v-e="['a:row:clear']" class="nc-project-menu-item">{{ $t('activity.clearCell') }}</div>
</a-menu-item> </a-menu-item>
<a-menu-item v-if="contextMenuTarget" @click="addEmptyRow(contextMenuTarget.row + 1)"> <a-menu-item v-if="contextMenuTarget" @click="addEmptyRow(contextMenuTarget.row + 1)">
<div v-t="['a:row:insert']" class="nc-project-menu-item"> <div v-e="['a:row:insert']" class="nc-project-menu-item">
<!-- Insert New Row --> <!-- Insert New Row -->
{{ $t('activity.insertRow') }} {{ $t('activity.insertRow') }}
</div> </div>

2
packages/nc-gui/components/smartsheet/expanded-form/Comments.vue

@ -57,7 +57,7 @@ watch(
<div class="p-0"> <div class="p-0">
<div class="flex justify-center"> <div class="flex justify-center">
<!-- Comments only --> <!-- Comments only -->
<a-checkbox v-model:checked="commentsOnly" v-t="['c:row-expand:comment-only']" @change="loadCommentsAndLogs" <a-checkbox v-model:checked="commentsOnly" v-e="['c:row-expand:comment-only']" @change="loadCommentsAndLogs"
>{{ $t('labels.commentsOnly') }}<span class="text-[11px] text-gray-500"></span> >{{ $t('labels.commentsOnly') }}<span class="text-[11px] text-gray-500"></span>
</a-checkbox> </a-checkbox>
</div> </div>

2
packages/nc-gui/components/smartsheet/expanded-form/Header.vue

@ -64,7 +64,7 @@ const iconColor = '#1890ff'
</template> </template>
<MdiCommentTextOutline <MdiCommentTextOutline
v-if="isUIAllowed('rowComments') && !isNew" v-if="isUIAllowed('rowComments') && !isNew"
v-t="['c:row-expand:comment-toggle']" v-e="['c:row-expand:comment-toggle']"
class="cursor-pointer select-none nc-toggle-comments text-gray-500" class="cursor-pointer select-none nc-toggle-comments text-gray-500"
@click="commentsDrawer = !commentsDrawer" @click="commentsDrawer = !commentsDrawer"
/> />

2
packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -151,7 +151,7 @@ function onStopEdit() {
@dblclick.stop="isUIAllowed('virtualViewsCreateOrEdit') && onDblClick()" @dblclick.stop="isUIAllowed('virtualViewsCreateOrEdit') && onDblClick()"
@click.stop="onClick" @click.stop="onClick"
> >
<div v-t="['a:view:open', { view: vModel.type }]" class="text-xs flex items-center w-full gap-2"> <div v-e="['a:view:open', { view: vModel.type }]" class="text-xs flex items-center w-full gap-2">
<div class="flex w-auto"> <div class="flex w-auto">
<MdiDrag <MdiDrag
class="nc-drag-icon hidden group-hover:block transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 !cursor-move" class="nc-drag-icon hidden group-hover:block transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 !cursor-move"

2
packages/nc-gui/components/smartsheet/sidebar/index.vue

@ -34,7 +34,7 @@ const { $e } = useNuxtApp()
provide(ViewListInj, views) provide(ViewListInj, views)
/** Sidebar visible */ /** Sidebar visible */
const { isOpen } = useSidebar({ storageKey: 'nc-right-sidebar' }) const { isOpen } = useSidebar({ storageKey: 'nc-right-sidebar', isOpen: true })
const sidebarCollapsed = computed(() => !isOpen.value) const sidebarCollapsed = computed(() => !isOpen.value)

2
packages/nc-gui/components/smartsheet/sidebar/toolbar/ToggleDrawer.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
/** Sidebar visible */ /** Sidebar visible */
const { isOpen, toggle } = useSidebar({ storageKey: 'nc-right-sidebar' }) const { isOpen, toggle } = useSidebar({ storageKey: 'nc-right-sidebar', isOpen: true })
</script> </script>
<template> <template>

2
packages/nc-gui/components/tabs/Smartsheet.vue

@ -42,7 +42,7 @@ const openNewRecordFormHook = createEventHook<void>()
const { isGallery, isGrid, isForm, isKanban, isLocked } = useProvideSmartsheetStore(activeView as Ref<TableType>, meta) const { isGallery, isGrid, isForm, isKanban, isLocked } = useProvideSmartsheetStore(activeView as Ref<TableType>, meta)
// provide the sidebar injection state // provide the sidebar injection state
provideSidebar({ storageKey: 'nc-right-sidebar' }) provideSidebar({ storageKey: 'nc-right-sidebar', isOpen: true })
// todo: move to store // todo: move to store
provide(MetaInj, meta) provide(MetaInj, meta)

6
packages/nc-gui/components/tabs/auth/UserManagement.vue

@ -201,7 +201,7 @@ watchDebounced(searchText, () => loadUsers(), { debounce: 300, maxWait: 600 })
</div> </div>
<div class="flex flex-row space-x-1"> <div class="flex flex-row space-x-1">
<a-button v-t="['a:user:reload']" size="middle" type="text" @click="loadUsers()"> <a-button v-e="['a:user:reload']" size="middle" type="text" @click="loadUsers()">
<div class="flex flex-row justify-center items-center caption capitalize space-x-1"> <div class="flex flex-row justify-center items-center caption capitalize space-x-1">
<MdiReload class="text-gray-500" /> <MdiReload class="text-gray-500" />
<div class="text-gray-500">{{ $t('general.reload') }}</div> <div class="text-gray-500">{{ $t('general.reload') }}</div>
@ -209,7 +209,7 @@ watchDebounced(searchText, () => loadUsers(), { debounce: 300, maxWait: 600 })
</a-button> </a-button>
<a-button <a-button
v-if="isUIAllowed('newUser')" v-if="isUIAllowed('newUser')"
v-t="['c:user:invite']" v-e="['c:user:invite']"
size="middle" size="middle"
type="primary" type="primary"
ghost ghost
@ -282,7 +282,7 @@ watchDebounced(searchText, () => loadUsers(), { debounce: 300, maxWait: 600 })
<template #title> <template #title>
<span>{{ $t('activity.deleteUser') }}</span> <span>{{ $t('activity.deleteUser') }}</span>
</template> </template>
<a-button v-t="['c:user:delete']" type="text" class="!rounded-md nc-user-delete" @click="onDelete(user)"> <a-button v-e="['c:user:delete']" type="text" class="!rounded-md nc-user-delete" @click="onDelete(user)">
<template #icon> <template #icon>
<MdiDeleteOutline class="flex mx-auto h-[1.1rem] text-gray-500" /> <MdiDeleteOutline class="flex mx-auto h-[1.1rem] text-gray-500" />
</template> </template>

13
packages/nc-gui/components/virtual-cell/Lookup.vue

@ -2,7 +2,18 @@
import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { CellUrlDisableOverlayInj, CellValueInj, ColumnInj, MetaInj, ReadonlyInj, computed, inject, provide, useColumn, useMetas } from '#imports' import {
CellUrlDisableOverlayInj,
CellValueInj,
ColumnInj,
MetaInj,
ReadonlyInj,
computed,
inject,
provide,
useColumn,
useMetas,
} from '#imports'
const { metas, getMeta } = useMetas() const { metas, getMeta } = useMetas()

2
packages/nc-gui/components/webhook/Drawer.vue

@ -38,7 +38,7 @@ async function editHook(hook: Record<string, any>) {
<WebhookList v-else @edit="editHook" @add="editOrAdd = true" /> <WebhookList v-else @edit="editHook" @add="editOrAdd = true" />
</a-layout-content> </a-layout-content>
<a-layout-footer class="!bg-white flex"> <a-layout-footer class="!bg-white flex">
<a-button v-t="['e:hiring']" class="mx-auto mb-4" href="https://angel.co/company/nocodb" target="_blank" size="large"> <a-button v-e="['e:hiring']" class="mx-auto mb-4" href="https://angel.co/company/nocodb" target="_blank" size="large">
🚀 {{ $t('labels.weAreHiring') }}! 🚀 🚀 {{ $t('labels.weAreHiring') }}! 🚀
</a-button> </a-button>
</a-layout-footer> </a-layout-footer>

2
packages/nc-gui/components/webhook/List.vue

@ -58,7 +58,7 @@ onMounted(() => {
<div class="mb-2"> <div class="mb-2">
<div class="float-left font-bold text-xl mt-2 mb-4">{{ meta.title }} : Webhooks</div> <div class="float-left font-bold text-xl mt-2 mb-4">{{ meta.title }} : Webhooks</div>
<a-button <a-button
v-t="['c:webhook:add']" v-e="['c:webhook:add']"
class="float-right nc-btn-create-webhook" class="float-right nc-btn-create-webhook"
type="primary" type="primary"
size="large" size="large"

48
packages/nc-gui/composables/useCellUrlConfig.ts

@ -1,48 +1,48 @@
import type { ComputedRef } from 'vue' import type { MaybeRef } from '@vueuse/core'
import { useRoute } from '#imports' import { computed, unref, useRoute } from '#imports'
export interface CellUrlOptions { export interface CellUrlOptions {
behavior?: string behavior?: string
overlay?: string overlay?: string
} }
const parseUrlRules = (serialized: string | undefined): Array<[RegExp, CellUrlOptions]> | undefined => { type ParsedRules = [RegExp, CellUrlOptions]
const parseUrlRules = (serialized?: string): ParsedRules[] | undefined => {
if (!serialized) return undefined if (!serialized) return undefined
try { try {
const rules: Array<[RegExp, {}]> = Object.entries(JSON.parse(serialized)).map(([key, value]) => [ return Object.entries(JSON.parse(serialized)).map(([key, value]) => [new RegExp(key), value] as ParsedRules)
new RegExp(key),
value as {},
])
return rules
} catch (err) { } catch (err) {
console.error(err) console.error(err)
return undefined return undefined
} }
} }
const [useProvideCellUrlConfig, useCellUrlGeneralConfig] = useInjectionState(() => { export function useCellUrlConfig(url?: MaybeRef<string>) {
const route = useRoute() const route = useRoute()
return { const config = $computed(() => ({
behavior: route.query.url_behavior as string | undefined, behavior: route.query.url_behavior as string | undefined,
overlay: route.query.url_overlay as string | undefined, overlay: route.query.url_overlay as string | undefined,
rules: parseUrlRules(route.query.url_rules as string), rules: parseUrlRules(route.query.url_rules as string),
} }))
}, 'cell-url-config')
const options = computed(() => {
export { useProvideCellUrlConfig } const options = { behavior: config.behavior, overlay: config.overlay }
export function useCellUrlConfig(url: ComputedRef<string>) { if (config.rules && (!config.behavior || !config.overlay)) {
const config = useCellUrlGeneralConfig() for (const [regex, value] of config.rules) {
if (!config) return undefined if (unref(url)?.match(regex)) return Object.assign(options, value)
return computed(() => {
const { behavior, overlay, rules } = config
const options = { behavior, overlay }
if (rules && (!behavior || !overlay)) {
for (const [regex, value] of rules) {
if (url.value.match(regex)) return Object.assign(options, value)
} }
} }
return options return options
}) })
return {
cellUrlConfig: config,
cellUrlOptions: options,
}
} }

6
packages/nc-gui/layouts/base.vue

@ -41,7 +41,7 @@ hooks.hook('page:finish', () => {
> >
<div <div
v-if="!route.params.projectType" v-if="!route.params.projectType"
v-t="['c:navbar:home']" v-e="['c:navbar:home']"
class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105 nc-noco-brand-icon" class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105 nc-noco-brand-icon"
@click="navigateTo('/')" @click="navigateTo('/')"
> >
@ -75,7 +75,7 @@ hooks.hook('page:finish', () => {
<template #overlay> <template #overlay>
<a-menu class="!py-0 leading-8 !rounded"> <a-menu class="!py-0 leading-8 !rounded">
<a-menu-item key="0" class="!rounded-t"> <a-menu-item key="0" class="!rounded-t">
<nuxt-link v-t="['c:navbar:user:email']" class="nc-project-menu-item group !no-underline" to="/user"> <nuxt-link v-e="['c:navbar:user:email']" class="nc-project-menu-item group !no-underline" to="/user">
<MdiAt class="mt-1 group-hover:text-accent" />&nbsp; <MdiAt class="mt-1 group-hover:text-accent" />&nbsp;
<span class="prose group-hover:text-primary"> {{ email }}</span> <span class="prose group-hover:text-primary"> {{ email }}</span>
@ -85,7 +85,7 @@ hooks.hook('page:finish', () => {
<a-menu-divider class="!m-0" /> <a-menu-divider class="!m-0" />
<a-menu-item key="1" class="!rounded-b group"> <a-menu-item key="1" class="!rounded-b group">
<div v-t="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout"> <div v-e="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout">
<MdiLogout class="group-hover:text-accent" />&nbsp; <MdiLogout class="group-hover:text-accent" />&nbsp;
<span class="prose group-hover:text-primary"> <span class="prose group-hover:text-primary">

2
packages/nc-gui/lib/types.ts

@ -1,6 +1,6 @@
import type { FilterType } from 'nocodb-sdk' import type { FilterType } from 'nocodb-sdk'
import type { I18n } from 'vue-i18n' import type { I18n } from 'vue-i18n'
import type { Language, Role } from './enums' import type { Role } from './enums'
export interface User { export interface User {
id: string id: string

1
packages/nc-gui/package.json

@ -1,5 +1,6 @@
{ {
"private": true, "private": true,
"web-types": "web-types.json",
"scripts": { "scripts": {
"build": "nuxi build", "build": "nuxi build",
"dev": "nuxi dev", "dev": "nuxi dev",

18
packages/nc-gui/pages/[projectType]/[projectId]/index.vue

@ -177,7 +177,7 @@ if (type && name) {
> >
<div <div
v-if="isOpen && !isSharedBase" v-if="isOpen && !isSharedBase"
v-t="['c:navbar:home']" v-e="['c:navbar:home']"
class="w-[40px] min-w-[40px] transition-all duration-200 p-1 cursor-pointer transform hover:scale-105 nc-noco-brand-icon" class="w-[40px] min-w-[40px] transition-all duration-200 p-1 cursor-pointer transform hover:scale-105 nc-noco-brand-icon"
@click="navigateTo('/')" @click="navigateTo('/')"
> >
@ -245,7 +245,7 @@ if (type && name) {
<!-- Copy Project Info --> <!-- Copy Project Info -->
<a-menu-item key="copy"> <a-menu-item key="copy">
<div <div
v-t="['c:navbar:user:copy-proj-info']" v-e="['c:navbar:user:copy-proj-info']"
class="nc-project-menu-item group" class="nc-project-menu-item group"
@click.stop="copyProjectInfo" @click.stop="copyProjectInfo"
> >
@ -260,7 +260,7 @@ if (type && name) {
<a-menu-item key="api"> <a-menu-item key="api">
<div <div
v-if="isUIAllowed('apiDocs')" v-if="isUIAllowed('apiDocs')"
v-t="['e:api-docs']" v-e="['e:api-docs']"
class="nc-project-menu-item group" class="nc-project-menu-item group"
@click.stop="openLink(`/api/v1/db/meta/projects/${route.params.projectId}/swagger`, appInfo.ncSiteUrl)" @click.stop="openLink(`/api/v1/db/meta/projects/${route.params.projectId}/swagger`, appInfo.ncSiteUrl)"
> >
@ -271,7 +271,7 @@ if (type && name) {
<!-- Copy Auth Token --> <!-- Copy Auth Token -->
<a-menu-item key="copy"> <a-menu-item key="copy">
<div v-t="['a:navbar:user:copy-auth-token']" class="nc-project-menu-item group" @click.stop="copyAuthToken"> <div v-e="['a:navbar:user:copy-auth-token']" class="nc-project-menu-item group" @click.stop="copyAuthToken">
<MdiScriptTextKeyOutline class="group-hover:text-accent" /> <MdiScriptTextKeyOutline class="group-hover:text-accent" />
{{ $t('activity.account.authToken') }} {{ $t('activity.account.authToken') }}
</div> </div>
@ -283,7 +283,7 @@ if (type && name) {
<a-menu-item key="teamAndSettings"> <a-menu-item key="teamAndSettings">
<div <div
v-if="isUIAllowed('settings')" v-if="isUIAllowed('settings')"
v-t="['c:navdraw:project-settings']" v-e="['c:navdraw:project-settings']"
class="nc-project-menu-item group" class="nc-project-menu-item group"
@click="toggleDialog(true, 'teamAndAuth')" @click="toggleDialog(true, 'teamAndAuth')"
> >
@ -364,7 +364,7 @@ if (type && name) {
<!-- Preview As --> <!-- Preview As -->
<a-sub-menu v-if="isUIAllowed('previewAs')" key="preview-as"> <a-sub-menu v-if="isUIAllowed('previewAs')" key="preview-as">
<template #title> <template #title>
<div v-t="['c:navdraw:preview-as']" class="nc-project-menu-item group"> <div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group">
<MdiFileEyeOutline class="group-hover:text-accent" /> <MdiFileEyeOutline class="group-hover:text-accent" />
{{ $t('activity.previewAs') }} {{ $t('activity.previewAs') }}
@ -421,7 +421,7 @@ if (type && name) {
<template #expandIcon></template> <template #expandIcon></template>
<a-menu-item key="0" class="!rounded-t"> <a-menu-item key="0" class="!rounded-t">
<nuxt-link v-t="['c:navbar:user:email']" class="nc-project-menu-item group !no-underline" to="/user"> <nuxt-link v-e="['c:navbar:user:email']" class="nc-project-menu-item group !no-underline" to="/user">
<MdiAt class="mt-1 group-hover:text-accent" />&nbsp; <MdiAt class="mt-1 group-hover:text-accent" />&nbsp;
<span class="prose-sm">{{ email }}</span> <span class="prose-sm">{{ email }}</span>
@ -429,7 +429,7 @@ if (type && name) {
</a-menu-item> </a-menu-item>
<a-menu-item key="1" class="!rounded-b"> <a-menu-item key="1" class="!rounded-b">
<div v-t="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout"> <div v-e="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout">
<MdiLogout class="group-hover:(!text-accent)" />&nbsp; <MdiLogout class="group-hover:(!text-accent)" />&nbsp;
<span class="prose-sm nc-user-menu-signout"> <span class="prose-sm nc-user-menu-signout">
@ -448,7 +448,7 @@ if (type && name) {
class="nc-sidebar-left-toggle-icon hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row flex items-center px-2" class="nc-sidebar-left-toggle-icon hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row flex items-center px-2"
> >
<MdiBackburger <MdiBackburger
v-t="['c:grid:toggle-navdraw']" v-e="['c:grid:toggle-navdraw']"
class="cursor-pointer transform transition-transform duration-500" class="cursor-pointer transform transition-transform duration-500"
:class="{ 'rotate-180': !isOpen }" :class="{ 'rotate-180': !isOpen }"
@click="toggle(!isOpen)" @click="toggle(!isOpen)"

2
packages/nc-gui/pages/[projectType]/[projectId]/index/index.vue

@ -39,7 +39,7 @@ function onEdit(targetKey: number, action: 'add' | 'remove' | string) {
class="nc-sidebar-left-toggle-icon hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row py-2 px-3" class="nc-sidebar-left-toggle-icon hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row py-2 px-3"
> >
<MdiMenu <MdiMenu
v-t="['c:grid:toggle-navdraw']" v-e="['c:grid:toggle-navdraw']"
class="cursor-pointer transform transition-transform duration-500 text-white" class="cursor-pointer transform transition-transform duration-500 text-white"
:class="{ 'rotate-180': !isOpen }" :class="{ 'rotate-180': !isOpen }"
@click="toggle(!isOpen)" @click="toggle(!isOpen)"

158
packages/nc-gui/pages/[projectType]/form/[viewId]/index.vue

@ -36,97 +36,99 @@ function isRequired(_columnObj: Record<string, any>, required = false) {
<div <div
class="bg-white relative flex flex-col justify-center gap-2 w-full lg:max-w-1/2 max-w-500px m-auto p-8 md:(rounded-lg border-1 border-gray-200 shadow-xl)" class="bg-white relative flex flex-col justify-center gap-2 w-full lg:max-w-1/2 max-w-500px m-auto p-8 md:(rounded-lg border-1 border-gray-200 shadow-xl)"
> >
<general-noco-icon class="color-transition hover:(ring ring-accent)" :class="[isLoading ? 'animated-bg-gradient' : '']" /> <template v-if="sharedFormView">
<general-noco-icon class="color-transition hover:(ring ring-accent)" :class="[isLoading ? 'animated-bg-gradient' : '']" />
<h1 class="prose-2xl font-bold self-center my-4">{{ sharedFormView.heading }}</h1> <h1 class="prose-2xl font-bold self-center my-4">{{ sharedFormView.heading }}</h1>
<h2 v-if="sharedFormView.subheading" class="prose-lg text-gray-500 self-center">{{ sharedFormView.subheading }}</h2> <h2 v-if="sharedFormView.subheading" class="prose-lg text-gray-500 self-center">{{ sharedFormView.subheading }}</h2>
<a-alert v-if="notFound" type="warning" class="my-4 text-center" message="Not found" /> <a-alert v-if="notFound" type="warning" class="my-4 text-center" message="Not found" />
<template v-else-if="submitted"> <template v-else-if="submitted">
<div class="flex justify-center"> <div class="flex justify-center">
<div v-if="sharedFormView" class="min-w-350px mt-3"> <div v-if="sharedFormView" class="min-w-350px mt-3">
<a-alert <a-alert
type="success" type="success"
class="my-4 text-center" class="my-4 text-center"
outlined outlined
:message="sharedFormView.success_msg || 'Successfully submitted form data'" :message="sharedFormView.success_msg || 'Successfully submitted form data'"
/> />
<p v-if="sharedFormView.show_blank_form" class="text-xs text-gray-500 text-center my-4"> <p v-if="sharedFormView.show_blank_form" class="text-xs text-gray-500 text-center my-4">
New form will be loaded after {{ secondsRemain }} seconds New form will be loaded after {{ secondsRemain }} seconds
</p> </p>
<div v-if="sharedFormView.submit_another_form" class="text-center"> <div v-if="sharedFormView.submit_another_form" class="text-center">
<a-button type="primary" @click="submitted = false"> Submit Another Form</a-button> <a-button type="primary" @click="submitted = false"> Submit Another Form</a-button>
</div>
</div> </div>
</div> </div>
</div> </template>
</template>
<template v-else-if="sharedFormView">
<template v-else-if="sharedFormView"> <div class="nc-form-wrapper">
<div class="nc-form-wrapper"> <div class="nc-form h-full max-w-3/4 mx-auto">
<div class="nc-form h-full max-w-3/4 mx-auto"> <div v-for="(field, index) in formColumns" :key="index" class="flex flex-col my-6 gap-2">
<div v-for="(field, index) in formColumns" :key="index" class="flex flex-col my-6 gap-2"> <div class="flex nc-form-column-label">
<div class="flex nc-form-column-label"> <SmartsheetHeaderVirtualCell
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(field)"
v-if="isVirtualCol(field)" :column="{ ...field, title: field.label || field.title }"
:column="{ ...field, title: field.label || field.title }" :required="isRequired(field, field.required)"
:required="isRequired(field, field.required)" :hide-menu="true"
:hide-menu="true" />
/>
<SmartsheetHeaderCell
<SmartsheetHeaderCell v-else
v-else :column="{ ...field, title: field.label || field.title }"
:column="{ ...field, title: field.label || field.title }" :required="isRequired(field, field.required)"
:required="isRequired(field, field.required)" :hide-menu="true"
:hide-menu="true" />
/> </div>
<div v-if="isVirtualCol(field)" class="mt-0">
<SmartsheetVirtualCell
class="mt-0 nc-input"
:class="`nc-form-input-${field.title.replaceAll(' ', '')}`"
:column="field"
/>
<div v-if="field.description" class="text-gray-500 text-[10px] mb-2 ml-1">{{ field.description }}</div>
<template v-if="v$.virtual.$dirty && v$.virtual?.[field.title]">
<div v-for="error of v$.virtual[field.title].$errors" :key="error" class="text-xs text-red-500">
{{ error.$message }}
</div>
</template>
</div>
<div v-else class="mt-0">
<SmartsheetCell
v-model="formState[field.title]"
class="nc-input"
:class="`nc-form-input-${field.title.replaceAll(' ', '')}`"
:column="field"
:edit-enabled="true"
/>
<div v-if="field.description" class="text-gray-500 text-[10px] mb-2 ml-1">{{ field.description }}</div>
<template v-if="v$.localState.$dirty && v$.localState?.[field.title]">
<div v-for="error of v$.localState[field.title].$errors" :key="error" class="text-xs text-red-500">
{{ error.$message }}
</div>
</template>
</div>
</div> </div>
<div v-if="isVirtualCol(field)" class="mt-0"> <div class="text-center my-9">
<SmartsheetVirtualCell <button type="submit" class="submit" @click="submitForm">
class="mt-0 nc-input" {{ $t('general.submit') }}
:class="`nc-form-input-${field.title.replaceAll(' ', '')}`" </button>
:column="field"
/>
<div v-if="field.description" class="text-gray-500 text-[10px] mb-2 ml-1">{{ field.description }}</div>
<template v-if="v$.virtual.$dirty && v$.virtual?.[field.title]">
<div v-for="error of v$.virtual[field.title].$errors" :key="error" class="text-xs text-red-500">
{{ error.$message }}
</div>
</template>
</div>
<div v-else class="mt-0">
<SmartsheetCell
v-model="formState[field.title]"
class="nc-input"
:class="`nc-form-input-${field.title.replaceAll(' ', '')}`"
:column="field"
:edit-enabled="true"
/>
<div v-if="field.description" class="text-gray-500 text-[10px] mb-2 ml-1">{{ field.description }}</div>
<template v-if="v$.localState.$dirty && v$.localState?.[field.title]">
<div v-for="error of v$.localState[field.title].$errors" :key="error" class="text-xs text-red-500">
{{ error.$message }}
</div>
</template>
</div> </div>
</div> </div>
<div class="text-center my-9">
<button type="submit" class="submit" @click="submitForm">
{{ $t('general.submit') }}
</button>
</div>
</div> </div>
</div> </template>
</template> </template>
<a-modal <a-modal

3
packages/nc-gui/pages/[projectType]/view/[viewId].vue

@ -8,7 +8,6 @@ import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
provide, provide,
ref, ref,
useProvideCellUrlConfig,
useRoute, useRoute,
useSharedView, useSharedView,
} from '#imports' } from '#imports'
@ -25,8 +24,6 @@ const reloadEventHook = createEventHook<void>()
provide(ReloadViewDataHookInj, reloadEventHook) provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, true) provide(ReadonlyInj, true)
useProvideCellUrlConfig()
const { loadSharedView } = useSharedView() const { loadSharedView } = useSharedView()
const showPassword = ref(false) const showPassword = ref(false)

2
packages/nc-gui/pages/index/index/[projectId].vue

@ -95,7 +95,7 @@ onMounted(async () => {
</a-form-item> </a-form-item>
<div class="text-center"> <div class="text-center">
<button v-t="['a:project:edit:rename']" type="submit" class="submit"> <button v-e="['a:project:edit:rename']" type="submit" class="submit">
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<MaterialSymbolsRocketLaunchOutline /> <MaterialSymbolsRocketLaunchOutline />
{{ $t('general.edit') }} {{ $t('general.edit') }}

8
packages/nc-gui/pages/index/index/index.vue

@ -119,7 +119,7 @@ const getProjectPrimary = (project: ProjectType) => {
:class="isLoading ? 'animate-spin ring ring-gray-200' : ''" :class="isLoading ? 'animate-spin ring ring-gray-200' : ''"
> >
<MdiRefresh <MdiRefresh
v-t="['a:project:refresh']" v-e="['a:project:refresh']"
class="text-xl text-gray-500 group-hover:text-accent cursor-pointer" class="text-xl text-gray-500 group-hover:text-accent cursor-pointer"
:class="isLoading ? '!text-primary' : ''" :class="isLoading ? '!text-primary' : ''"
@click="loadProjects" @click="loadProjects"
@ -149,7 +149,7 @@ const getProjectPrimary = (project: ProjectType) => {
<a-menu class="!py-0 rounded"> <a-menu class="!py-0 rounded">
<a-menu-item> <a-menu-item>
<div <div
v-t="['c:project:create:xcdb']" v-e="['c:project:create:xcdb']"
class="nc-project-menu-item group nc-create-xc-db-project" class="nc-project-menu-item group nc-create-xc-db-project"
@click="navigateTo('/create')" @click="navigateTo('/create')"
> >
@ -161,7 +161,7 @@ const getProjectPrimary = (project: ProjectType) => {
<a-menu-item> <a-menu-item>
<div <div
v-t="['c:project:create:extdb']" v-e="['c:project:create:extdb']"
class="nc-project-menu-item group nc-create-external-db-project" class="nc-project-menu-item group nc-create-external-db-project"
@click="navigateTo('/create-external')" @click="navigateTo('/create-external')"
> >
@ -252,7 +252,7 @@ const getProjectPrimary = (project: ProjectType) => {
<a-table-column key="id" :title="$t('labels.actions')" data-index="id"> <a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }"> <template #default="{ text, record }">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<MdiEditOutline v-t="['c:project:edit:rename']" class="nc-action-btn" @click.stop="navigateTo(`/${text}`)" /> <MdiEditOutline v-e="['c:project:edit:rename']" class="nc-action-btn" @click.stop="navigateTo(`/${text}`)" />
<MdiDeleteOutline class="nc-action-btn" @click.stop="deleteProject(record)" /> <MdiDeleteOutline class="nc-action-btn" @click.stop="deleteProject(record)" />
</div> </div>

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

@ -63,7 +63,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
}, },
} }
nuxtApp.vueApp.directive('t', { nuxtApp.vueApp.directive('e', {
created(el, binding, vnode) { created(el, binding, vnode) {
if (vnode.el) vnode.el.addEventListener('click', getListener(binding)) if (vnode.el) vnode.el.addEventListener('click', getListener(binding))
else el.addEventListener('click', getListener(binding)) else el.addEventListener('click', getListener(binding))

20
packages/nc-gui/web-types.json

@ -0,0 +1,20 @@
{
"$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/v2-preview/web-types.json",
"name": "nc-gui",
"framework": "vue",
"version": "1.0.0",
"contributions": {
"html": {
"vue-directives": [
{
"name": "e",
"description": "Telemetry directive"
},
{
"name": "t",
"description": "I18n directive"
}
]
}
}
}
Loading…
Cancel
Save