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": [
"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,

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://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="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>
</tbody>
</table>

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

@ -42,9 +42,15 @@ function onClick() {
<template>
<div
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
:is="getMdiIcon(vModel ? checkboxMeta.icon.checked : checkboxMeta.icon.unchecked)"
:style="{

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

@ -1,22 +1,35 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
import { CellUrlDisableOverlayInj, ColumnInj, EditModeInj, computed, inject, isValidURL, ref } from '#imports'
import MiCircleWarning from '~icons/mi/circle-warning'
import {
CellUrlDisableOverlayInj,
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 {
modelValue?: string | null
}
const { modelValue: value } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const { t } = useI18n()
const column = inject(ColumnInj)!
const editEnabled = inject(EditModeInj)!
const disableOverlay = inject(CellUrlDisableOverlayInj)
// Used in the logic of when to display error since we are not storing the url if its not valid
const localState = ref(value)
@ -40,7 +53,8 @@ const url = computed(() => {
return `https://${value}`
})
const urlOptions = useCellUrlConfig(url)
const { cellUrlOptions } = useCellUrlConfig(url)
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" />
<nuxt-link
v-else-if="isValid && !urlOptions?.overlay"
v-else-if="isValid && !cellUrlOptions?.overlay"
class="z-3 text-sm underline hover:opacity-75"
:to="url"
:target="urlOptions?.behavior === 'replace' ? undefined : '_blank'"
:target="cellUrlOptions?.behavior === 'replace' ? undefined : '_blank'"
>
{{ value }}
</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"
:to="url"
:target="urlOptions?.behavior === 'replace' ? undefined : '_blank'"
:target="cellUrlOptions?.behavior === 'replace' ? undefined : '_blank'"
>
{{ urlOptions.overlay }}
{{ cellUrlOptions.overlay }}
</nuxt-link>
<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
v-t="['e:datasource:import-request']"
v-e="['e:datasource:import-request']"
href="https://github.com/nocodb/nocodb/issues/2052"
target="_blank"
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
v-for="table of tables"
:key="table.id"
v-t="['a:table:open']"
v-e="['a:table:open']"
:class="[
{ hidden: !filteredTables?.includes(table), active: activeTable === 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-row justify-end items-center w-full mb-4">
<!-- 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">
<MdiReload :class="{ 'animate-infinite animate-spin !text-success': isLoading }" />
{{ $t('general.reload') }}
@ -112,7 +112,7 @@ const columns = [
<div class="flex place-content-center w-2/5">
<!-- Sync Now -->
<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">
<MdiDatabaseSync />
{{ $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-row items-center w-full mb-4 gap-2">
<!-- 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')
}}</a-checkbox>
</div>

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

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

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

@ -41,7 +41,7 @@ const openSwaggerLink = () => {
<template #before>
<a-list-item v-if="project">
<nuxt-link
v-t="['a:navbar:user:swagger']"
v-e="['a:navbar:user:swagger']"
class="!no-underline !text-current py-4 font-semibold"
target="_blank"
@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-item-group title="User Settings">
<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" />
&nbsp;
<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-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;
<span class="prose font-semibold text-gray-500 group-hover:text-black nc-user-menu-signout">
{{ $t('general.signOut') }}
@ -76,7 +76,7 @@ const logout = () => {
<a-menu-item class="active:(ring ring-accent)">
<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"
@click="navigateTo('/project/create')"
>
@ -87,7 +87,7 @@ const logout = () => {
<a-menu-item class="rounded-b active:(ring ring-accent)">
<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"
@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">
<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
v-t="['e:community:discourse']"
v-e="['e:community:discourse']"
class="icon flex items-center justify-center min-w-[43px]"
@click="open('https://community.nocodb.com/')"
>
<div class="discourse" />
</div>
<MdiReddit v-t="['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')" />
<MdiReddit v-e="['e:community:reddit']" class="icon text-[#FF4600]" @click="open('https://www.reddit.com/r/NocoDB/')" />
<MdiTwitter v-e="['e:community:twitter']" class="icon text-[#1DA1F2]" @click="open('https://twitter.com/NocoDB')" />
<MdiCalendarMonth
v-t="['e:community:book-demo']"
v-e="['e:community:book-demo']"
class="icon text-green-500"
@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>
<nuxt-link
v-t="['e:docs']"
v-e="['e:docs']"
class="text-primary !no-underline !text-current"
target="_blank"
to="https://docs.nocodb.com/"
@ -26,7 +26,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item>
<a-list-item>
<nuxt-link
v-t="['e:api-docs']"
v-e="['e:api-docs']"
class="text-primary !no-underline !text-current"
target="_blank"
to="https://apis.nocodb.com/"
@ -40,7 +40,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item>
<a-list-item>
<nuxt-link
v-t="['e:community:github']"
v-e="['e:community:github']"
class="text-primary !no-underline !text-current"
to="https://github.com/nocodb/nocodb"
target="_blank"
@ -66,7 +66,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item>
<a-list-item>
<nuxt-link
v-t="['e:community:book-demo']"
v-e="['e:community:book-demo']"
class="!no-underline !text-current"
to="https://calendly.com/nocodb-meeting"
target="_blank"
@ -82,7 +82,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item>
<a-list-item>
<nuxt-link
v-t="['e:community:discord']"
v-e="['e:community:discord']"
class="!no-underline !text-current"
to="https://discord.gg/5RgZmkW"
target="_blank"
@ -98,7 +98,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item>
<a-list-item>
<nuxt-link
v-t="['e:community:twitter']"
v-e="['e:community:twitter']"
class="!no-underline !text-current"
to="https://twitter.com/NocoDB"
target="_blank"
@ -113,7 +113,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</nuxt-link>
</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">
<!-- todo: i18n -->
<div class="ml-3">
@ -124,7 +124,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item>
<a-list-item>
<nuxt-link
v-t="['e:community:reddit']"
v-e="['e:community:reddit']"
class="!no-underline !text-current"
target="_blank"
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 { TableType } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import { ActiveViewInj, FieldsInj, IsPublicInj, MetaInj, ReadonlyInj, ReloadViewDataHookInj } from '~/context'
const { sharedView, meta, sorts, nestedFilters } = useSharedView()
@ -10,6 +9,7 @@ const { signedIn } = useGlobal()
const { loadProject } = useProject(meta?.value.project_id)
const reloadEventHook = createEventHook<void>()
provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, true)
provide(MetaInj, meta)

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

@ -14,7 +14,7 @@ const onClick = () => {
<a-tooltip placement="bottom">
<template #title> {{ $t('activity.addRow') }} </template>
<div
v-t="['c:row:add:grid-top']"
v-e="['c:row:add:grid-top']"
:class="{ 'group': !isLocked, 'disabled-ring': isLocked }"
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>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-filter-menu">
<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">
<MdiFilterOutline />
<!-- Filter -->
@ -80,7 +80,7 @@ const filterAutoSaveLoc = computed({
<div class="flex-1" />
<a-button
v-show="!filterAutoSave"
v-t="['a:filter:auto-apply']"
v-e="['a:filter:auto-apply']"
size="small"
class="text-xs ml-2"
@click="applyChanges"

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

@ -1,6 +1,6 @@
<template>
<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">
<MdiDownload class="group-hover:text-accent text-gray-500" />
<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>
<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" />
<!-- Download as CSV -->
{{ $t('activity.downloadCSV') }}
</div>
</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" />
<!-- Download as XLSX -->
{{ $t('activity.downloadExcel') }}

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

@ -118,7 +118,7 @@ const getIcon = (c: ColumnType) =>
<template>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-fields-menu">
<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">
<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>
<a-checkbox
v-model:checked="field.show"
v-t="['a:fields:show-hide']"
v-e="['a:fields:show-hide']"
class="shrink"
@change="saveOrUpdate(field, index)"
>

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

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

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

@ -114,7 +114,7 @@ watch(passwordProtected, (value) => {
<div>
<a-button
v-if="isUIAllowed('share-view') && !isSharedBase"
v-t="['c:view:share']"
v-e="['c:view:share']"
outlined
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="flex-1 h-min text-xs">{{ sharedViewUrl }}</div>
<!-- <v-spacer /> -->
<a v-t="['c:view:share:open-url']" :href="sharedViewUrl" target="_blank">
<a v-e="['c:view:share:open-url']" :href="sharedViewUrl" target="_blank">
<MdiOpenInNewIcon class="text-sm text-gray-500 mt-2" />
</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>
<a-collapse ghost>

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

@ -43,7 +43,7 @@ watch(
<template>
<a-dropdown offset-y class="" :trigger="['click']" overlay-class-name="nc-dropdown-sort-menu">
<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">
<MdiSort />
<!-- Sort -->

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

@ -82,7 +82,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<template>
<div>
<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">
<component
: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"
>
<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" />
<MaterialSymbolsChevronRightRounded
@ -130,7 +130,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-sub-menu key="download">
<template #title>
<!-- 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" />
{{ $t('general.download') }}
<div class="flex-1" />
@ -148,7 +148,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-sub-menu key="upload">
<!-- Upload -->
<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" />
{{ $t('general.upload') }}
<div class="flex-1" />
@ -163,7 +163,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-menu-item>
<div
v-if="isUIAllowed('csvImport') && !isView && !isPublicView"
v-t="['a:actions:upload-csv']"
v-e="['a:actions:upload-csv']"
class="nc-project-menu-item"
:class="{ disabled: isLocked }"
@click="!isLocked ? (quickImportDialog = true) : {}"
@ -180,7 +180,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-menu-item>
<div
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"
@click="sharedViewListDlg = true"
>
@ -192,7 +192,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-menu-item v-if="!isSqlView">
<div
v-if="isUIAllowed('webhook') && !isView && !isPublicView"
v-t="['c:actions:webhook']"
v-e="['c:actions:webhook']"
class="py-2 flex gap-2 items-center"
@click="showWebhookDrawer = true"
>
@ -203,7 +203,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-menu-item>
<div
v-if="!isSharedBase && !isPublicView"
v-t="['c:snippet:open']"
v-e="['c:snippet:open']"
class="py-2 flex gap-2 items-center"
@click="showApiSnippetDrawer = true"
>

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

@ -169,7 +169,7 @@ watch($$(activeLang), (newLang) => {
</a-select-option>
</a-select>
<a-button
v-t="[
v-e="[
'c:snippet:copy',
{ 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%]">
<a
v-t="['e:hiring']"
v-e="['e:hiring']"
class="px-4 py-2 ! rounded shadow"
href="https://angel.co/company/nocodb"
target="_blank"

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

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

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

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

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

@ -2,7 +2,6 @@
import type { ColumnType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
import {
ActiveViewInj,
CellUrlDisableOverlayInj,
@ -27,6 +26,7 @@ import {
ref,
useEventListener,
useGridViewColumnWidth,
useI18n,
useSmartsheetStoreOrThrow,
useUIPermission,
useViewData,
@ -425,7 +425,7 @@ onBeforeUnmount(async () => {
</th>
<th
v-if="!readOnly && !isLocked && isUIAllowed('add-column') && !isSqlView"
v-t="['c:column:add']"
v-e="['c:column:add']"
class="cursor-pointer"
@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)"
>
<MdiArrowExpand
v-t="['c:row-expand']"
v-e="['c:row-expand']"
class="select-none transform hover:(text-accent scale-120) nc-row-expand"
@click="expandForm(row, state)"
/>
@ -544,7 +544,7 @@ onBeforeUnmount(async () => {
<tr v-if="!isView && !isLocked && isUIAllowed('xcDatatableEditable') && !isSqlView">
<td
v-t="['c:row:add:grid-bottom']"
v-e="['c:row:add:grid-bottom']"
:colspan="visibleColLength + 1"
class="text-left pointer nc-grid-add-new-cell cursor-pointer"
@click="addEmptyRow()"
@ -564,14 +564,14 @@ onBeforeUnmount(async () => {
<template v-if="!isLocked && isUIAllowed('xcDatatableEditable')" #overlay>
<a-menu class="shadow !rounded !py-0" @click="contextMenu = false">
<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 -->
{{ $t('activity.deleteRow') }}
</div>
</a-menu-item>
<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 -->
{{ $t('activity.deleteSelectedRow') }}
</div>
@ -579,11 +579,11 @@ onBeforeUnmount(async () => {
<!-- Clear cell -->
<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 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 -->
{{ $t('activity.insertRow') }}
</div>

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

@ -57,7 +57,7 @@ watch(
<div class="p-0">
<div class="flex justify-center">
<!-- 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>
</a-checkbox>
</div>

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

@ -64,7 +64,7 @@ const iconColor = '#1890ff'
</template>
<MdiCommentTextOutline
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"
@click="commentsDrawer = !commentsDrawer"
/>

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

@ -151,7 +151,7 @@ function onStopEdit() {
@dblclick.stop="isUIAllowed('virtualViewsCreateOrEdit') && onDblClick()"
@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">
<MdiDrag
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)
/** Sidebar visible */
const { isOpen } = useSidebar({ storageKey: 'nc-right-sidebar' })
const { isOpen } = useSidebar({ storageKey: 'nc-right-sidebar', isOpen: true })
const sidebarCollapsed = computed(() => !isOpen.value)

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

@ -1,6 +1,6 @@
<script setup lang="ts">
/** Sidebar visible */
const { isOpen, toggle } = useSidebar({ storageKey: 'nc-right-sidebar' })
const { isOpen, toggle } = useSidebar({ storageKey: 'nc-right-sidebar', isOpen: true })
</script>
<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)
// provide the sidebar injection state
provideSidebar({ storageKey: 'nc-right-sidebar' })
provideSidebar({ storageKey: 'nc-right-sidebar', isOpen: true })
// todo: move to store
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 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">
<MdiReload class="text-gray-500" />
<div class="text-gray-500">{{ $t('general.reload') }}</div>
@ -209,7 +209,7 @@ watchDebounced(searchText, () => loadUsers(), { debounce: 300, maxWait: 600 })
</a-button>
<a-button
v-if="isUIAllowed('newUser')"
v-t="['c:user:invite']"
v-e="['c:user:invite']"
size="middle"
type="primary"
ghost
@ -282,7 +282,7 @@ watchDebounced(searchText, () => loadUsers(), { debounce: 300, maxWait: 600 })
<template #title>
<span>{{ $t('activity.deleteUser') }}</span>
</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>
<MdiDeleteOutline class="flex mx-auto h-[1.1rem] text-gray-500" />
</template>

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

@ -2,7 +2,18 @@
import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk'
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()

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" />
</a-layout-content>
<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') }}! 🚀
</a-button>
</a-layout-footer>

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

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

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

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

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

@ -41,7 +41,7 @@ hooks.hook('page:finish', () => {
>
<div
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"
@click="navigateTo('/')"
>
@ -75,7 +75,7 @@ hooks.hook('page:finish', () => {
<template #overlay>
<a-menu class="!py-0 leading-8 !rounded">
<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;
<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-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;
<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 { I18n } from 'vue-i18n'
import type { Language, Role } from './enums'
import type { Role } from './enums'
export interface User {
id: string

1
packages/nc-gui/package.json

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

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

@ -177,7 +177,7 @@ if (type && name) {
>
<div
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"
@click="navigateTo('/')"
>
@ -245,7 +245,7 @@ if (type && name) {
<!-- Copy Project Info -->
<a-menu-item key="copy">
<div
v-t="['c:navbar:user:copy-proj-info']"
v-e="['c:navbar:user:copy-proj-info']"
class="nc-project-menu-item group"
@click.stop="copyProjectInfo"
>
@ -260,7 +260,7 @@ if (type && name) {
<a-menu-item key="api">
<div
v-if="isUIAllowed('apiDocs')"
v-t="['e:api-docs']"
v-e="['e:api-docs']"
class="nc-project-menu-item group"
@click.stop="openLink(`/api/v1/db/meta/projects/${route.params.projectId}/swagger`, appInfo.ncSiteUrl)"
>
@ -271,7 +271,7 @@ if (type && name) {
<!-- Copy Auth Token -->
<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" />
{{ $t('activity.account.authToken') }}
</div>
@ -283,7 +283,7 @@ if (type && name) {
<a-menu-item key="teamAndSettings">
<div
v-if="isUIAllowed('settings')"
v-t="['c:navdraw:project-settings']"
v-e="['c:navdraw:project-settings']"
class="nc-project-menu-item group"
@click="toggleDialog(true, 'teamAndAuth')"
>
@ -364,7 +364,7 @@ if (type && name) {
<!-- Preview As -->
<a-sub-menu v-if="isUIAllowed('previewAs')" key="preview-as">
<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" />
{{ $t('activity.previewAs') }}
@ -421,7 +421,7 @@ if (type && name) {
<template #expandIcon></template>
<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;
<span class="prose-sm">{{ email }}</span>
@ -429,7 +429,7 @@ if (type && name) {
</a-menu-item>
<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;
<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"
>
<MdiBackburger
v-t="['c:grid:toggle-navdraw']"
v-e="['c:grid:toggle-navdraw']"
class="cursor-pointer transform transition-transform duration-500"
:class="{ 'rotate-180': !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"
>
<MdiMenu
v-t="['c:grid:toggle-navdraw']"
v-e="['c:grid:toggle-navdraw']"
class="cursor-pointer transform transition-transform duration-500 text-white"
:class="{ 'rotate-180': !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
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">
<div class="flex justify-center">
<div v-if="sharedFormView" class="min-w-350px mt-3">
<a-alert
type="success"
class="my-4 text-center"
outlined
:message="sharedFormView.success_msg || 'Successfully submitted form data'"
/>
<template v-else-if="submitted">
<div class="flex justify-center">
<div v-if="sharedFormView" class="min-w-350px mt-3">
<a-alert
type="success"
class="my-4 text-center"
outlined
: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">
New form will be loaded after {{ secondsRemain }} seconds
</p>
<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
</p>
<div v-if="sharedFormView.submit_another_form" class="text-center">
<a-button type="primary" @click="submitted = false"> Submit Another Form</a-button>
<div v-if="sharedFormView.submit_another_form" class="text-center">
<a-button type="primary" @click="submitted = false"> Submit Another Form</a-button>
</div>
</div>
</div>
</div>
</template>
<template v-else-if="sharedFormView">
<div class="nc-form-wrapper">
<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 class="flex nc-form-column-label">
<SmartsheetHeaderVirtualCell
v-if="isVirtualCol(field)"
:column="{ ...field, title: field.label || field.title }"
:required="isRequired(field, field.required)"
:hide-menu="true"
/>
<SmartsheetHeaderCell
v-else
:column="{ ...field, title: field.label || field.title }"
:required="isRequired(field, field.required)"
:hide-menu="true"
/>
</template>
<template v-else-if="sharedFormView">
<div class="nc-form-wrapper">
<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 class="flex nc-form-column-label">
<SmartsheetHeaderVirtualCell
v-if="isVirtualCol(field)"
:column="{ ...field, title: field.label || field.title }"
:required="isRequired(field, field.required)"
:hide-menu="true"
/>
<SmartsheetHeaderCell
v-else
:column="{ ...field, title: field.label || field.title }"
:required="isRequired(field, field.required)"
: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 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 class="text-center my-9">
<button type="submit" class="submit" @click="submitForm">
{{ $t('general.submit') }}
</button>
</div>
</div>
<div class="text-center my-9">
<button type="submit" class="submit" @click="submitForm">
{{ $t('general.submit') }}
</button>
</div>
</div>
</div>
</template>
</template>
<a-modal

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

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

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

@ -95,7 +95,7 @@ onMounted(async () => {
</a-form-item>
<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">
<MaterialSymbolsRocketLaunchOutline />
{{ $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' : ''"
>
<MdiRefresh
v-t="['a:project:refresh']"
v-e="['a:project:refresh']"
class="text-xl text-gray-500 group-hover:text-accent cursor-pointer"
:class="isLoading ? '!text-primary' : ''"
@click="loadProjects"
@ -149,7 +149,7 @@ const getProjectPrimary = (project: ProjectType) => {
<a-menu class="!py-0 rounded">
<a-menu-item>
<div
v-t="['c:project:create:xcdb']"
v-e="['c:project:create:xcdb']"
class="nc-project-menu-item group nc-create-xc-db-project"
@click="navigateTo('/create')"
>
@ -161,7 +161,7 @@ const getProjectPrimary = (project: ProjectType) => {
<a-menu-item>
<div
v-t="['c:project:create:extdb']"
v-e="['c:project:create:extdb']"
class="nc-project-menu-item group nc-create-external-db-project"
@click="navigateTo('/create-external')"
>
@ -252,7 +252,7 @@ const getProjectPrimary = (project: ProjectType) => {
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<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)" />
</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) {
if (vnode.el) vnode.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