mirror of
https://github.com/nocodb/nocodb.git
synced 2026-05-01 20:26:29 +00:00
chore(nc-gui): lint
This commit is contained in:
@@ -374,8 +374,10 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="h-[calc(100%_-_49px)] flex">
|
||||
<div ref="leftPaneContentRef"
|
||||
class="w-[480px] h-full relative flex flex-col nc-scrollbar-thin border-r-1 border-nc-border-purple-light">
|
||||
<div
|
||||
ref="leftPaneContentRef"
|
||||
class="w-[480px] h-full relative flex flex-col nc-scrollbar-thin border-r-1 border-nc-border-purple-light"
|
||||
>
|
||||
<!-- create base config panel -->
|
||||
<div class="flex-1 p-6 flex flex-col gap-6">
|
||||
<div class="text-sm font-bold text-nc-content-purple-dark dark:text-nc-content-purple-medium">
|
||||
@@ -385,27 +387,38 @@ onMounted(() => {
|
||||
<!-- Predefined tags -->
|
||||
|
||||
<template v-for="prompt of predefinedBasePrompts" :key="prompt.tag">
|
||||
<a-tag class="nc-ai-base-schema-tag nc-ai-suggested-tag relative" :class="{
|
||||
'nc-selected': prompt.description === aiFormState.prompt.trim(),
|
||||
'nc-disabled': !aiIntegrationAvailable || (aiLoading && callFunction === 'onPredictSchema'),
|
||||
}" :disabled="!aiIntegrationAvailable || (aiLoading && callFunction === 'onPredictSchema')"
|
||||
@mouseover="handleMouseOverTag(prompt.description)" @mouseleave="handleMouseLeaveTag"
|
||||
@click="handleUpdatePrompt(prompt.description)">
|
||||
<a-tag
|
||||
class="nc-ai-base-schema-tag nc-ai-suggested-tag relative"
|
||||
:class="{
|
||||
'nc-selected': prompt.description === aiFormState.prompt.trim(),
|
||||
'nc-disabled': !aiIntegrationAvailable || (aiLoading && callFunction === 'onPredictSchema'),
|
||||
}"
|
||||
:disabled="!aiIntegrationAvailable || (aiLoading && callFunction === 'onPredictSchema')"
|
||||
@mouseover="handleMouseOverTag(prompt.description)"
|
||||
@mouseleave="handleMouseLeaveTag"
|
||||
@click="handleUpdatePrompt(prompt.description)"
|
||||
>
|
||||
<div class="flex flex-row items-center gap-1 py-1 text-sm">
|
||||
<div>{{ prompt.tag }}</div>
|
||||
</div>
|
||||
<div v-if="prompt.description === aiFormState.prompt.trim()"
|
||||
class="bg-nc-fill-purple-dark text-nc-content-inverted-primary rounded-full absolute -right-[3px] -top-[4px] h-3 w-3 grid place-items-center">
|
||||
<div
|
||||
v-if="prompt.description === aiFormState.prompt.trim()"
|
||||
class="bg-nc-fill-purple-dark text-nc-content-inverted-primary rounded-full absolute -right-[3px] -top-[4px] h-3 w-3 grid place-items-center"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="none">
|
||||
<path d="M6.66659 2L2.99992 5.66667L1.33325 4" stroke="currentColor" stroke-width="1.33333"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path
|
||||
d="M6.66659 2L2.99992 5.66667L1.33325 4"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.33333"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<NcButton size="xs" type="text" icon-position="right" class="nc-show-more-tags-btn"
|
||||
@click="onToggleShowMore">
|
||||
<NcButton size="xs" type="text" icon-position="right" class="nc-show-more-tags-btn" @click="onToggleShowMore">
|
||||
{{ isExpandedPredefiendBasePromts ? $t('general.showLess') : $t('general.showMore') }}
|
||||
|
||||
<template #icon>
|
||||
@@ -414,11 +427,16 @@ onMounted(() => {
|
||||
</NcButton>
|
||||
</div>
|
||||
<div>
|
||||
<a-textarea ref="aiPromptInputRef" :value="aiFormState.onHoverTagPrompt || aiFormState.prompt"
|
||||
<a-textarea
|
||||
ref="aiPromptInputRef"
|
||||
:value="aiFormState.onHoverTagPrompt || aiFormState.prompt"
|
||||
placeholder="Type something..."
|
||||
class="!w-full !min-h-[120px] !rounded-lg mt-2 overflow-y-auto nc-scrollbar-thin nc-input-shadow nc-ai-input"
|
||||
size="middle" :disabled="!aiIntegrationAvailable || (aiLoading && callFunction === 'onPredictSchema')"
|
||||
:maxlength="8192" @update:value="aiFormState.prompt = $event" />
|
||||
size="middle"
|
||||
:disabled="!aiIntegrationAvailable || (aiLoading && callFunction === 'onPredictSchema')"
|
||||
:maxlength="8192"
|
||||
@update:value="aiFormState.prompt = $event"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a-collapse v-model:active-key="expansionPanel" ghost class="flex-1 flex flex-col">
|
||||
@@ -426,13 +444,22 @@ onMounted(() => {
|
||||
<a-collapse-panel :key="ExpansionPanelKeys.additionalDetails" collapsible="disabled">
|
||||
<template #header>
|
||||
<div class="flex">
|
||||
<NcButton size="small" type="text" icon-position="right" class="-ml-[7px]"
|
||||
@click="handleUpdateExpansionPanel(ExpansionPanelKeys.additionalDetails)">
|
||||
<NcButton
|
||||
size="small"
|
||||
type="text"
|
||||
icon-position="right"
|
||||
class="-ml-[7px]"
|
||||
@click="handleUpdateExpansionPanel(ExpansionPanelKeys.additionalDetails)"
|
||||
>
|
||||
{{ $t('title.additionalDetails') }}
|
||||
<template #icon>
|
||||
<GeneralIcon icon="arrowDown" class="transform transition-all opacity-80" :class="{
|
||||
'rotate-180': expansionPanel.includes(ExpansionPanelKeys.additionalDetails),
|
||||
}" />
|
||||
<GeneralIcon
|
||||
icon="arrowDown"
|
||||
class="transform transition-all opacity-80"
|
||||
:class="{
|
||||
'rotate-180': expansionPanel.includes(ExpansionPanelKeys.additionalDetails),
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
@@ -441,18 +468,25 @@ onMounted(() => {
|
||||
<div class="flex flex-col gap-6 pt-6">
|
||||
<div v-for="field of additionalDetails" :key="field.title" class="flex items-center gap-2">
|
||||
<div class="min-w-[120px] text-nc-content-gray">{{ field.title }}</div>
|
||||
<a-input v-model:value="aiFormState[field.key]" class="nc-input-sm nc-input-shadow nc-ai-input"
|
||||
hide-details :placeholder="field.placeholder"
|
||||
:disabled="!aiIntegrationAvailable || (aiLoading && callFunction === 'onPredictSchema')" />
|
||||
<a-input
|
||||
v-model:value="aiFormState[field.key]"
|
||||
class="nc-input-sm nc-input-shadow nc-ai-input"
|
||||
hide-details
|
||||
:placeholder="field.placeholder"
|
||||
:disabled="!aiIntegrationAvailable || (aiLoading && callFunction === 'onPredictSchema')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
<div class="sticky bottom-0 w-full bg-nc-bg-default px-6 pt-3 pb-6 border-t-1 flex flex-col gap-3" :class="{
|
||||
'border-nc-border-gray-medium': showBtnTopBorder,
|
||||
'border-transparent': !showBtnTopBorder,
|
||||
}">
|
||||
<div
|
||||
class="sticky bottom-0 w-full bg-nc-bg-default px-6 pt-3 pb-6 border-t-1 flex flex-col gap-3"
|
||||
:class="{
|
||||
'border-nc-border-gray-medium': showBtnTopBorder,
|
||||
'border-transparent': !showBtnTopBorder,
|
||||
}"
|
||||
>
|
||||
<div v-if="aiError" class="w-full flex items-start gap-3 bg-nc-bg-red-light rounded-lg p-4">
|
||||
<GeneralIcon icon="ncInfoSolid" class="flex-none !text-nc-content-red-dark w-6 h-6" />
|
||||
|
||||
@@ -467,20 +501,35 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="aiIntegrationAvailable" class="flex items-center gap-3">
|
||||
<NcButton size="small" :type="aiStep !== AI_STEP.MODIFY || isOldPromptChanged ? 'primary' : 'secondary'"
|
||||
theme="ai" class="w-1/2"
|
||||
<NcButton
|
||||
size="small"
|
||||
:type="aiStep !== AI_STEP.MODIFY || isOldPromptChanged ? 'primary' : 'secondary'"
|
||||
theme="ai"
|
||||
class="w-1/2"
|
||||
:disabled="!aiFormState.prompt?.trim() || (aiLoading && callFunction === 'onPredictSchema')"
|
||||
:loading="aiLoading && callFunction === 'onPredictSchema'" @click="onPredictSchema">
|
||||
:loading="aiLoading && callFunction === 'onPredictSchema'"
|
||||
@click="onPredictSchema"
|
||||
>
|
||||
<template #icon>
|
||||
<GeneralIcon icon="ncAutoAwesome" class="h-4 w-4" />
|
||||
</template>
|
||||
{{ $t('labels.suggestTablesViews') }}
|
||||
</NcButton>
|
||||
<NcButton v-e="['a:base:ai:create']" type="primary" size="small" theme="ai" class="w-1/2" :disabled="aiStep !== AI_STEP.MODIFY ||
|
||||
finalSchema?.tables?.length === 0 ||
|
||||
(aiLoading && callFunction === 'onCreateSchema') ||
|
||||
isOldPromptChanged
|
||||
" :loading="aiLoading && callFunction === 'onCreateSchema'" @click="onCreateSchema">
|
||||
<NcButton
|
||||
v-e="['a:base:ai:create']"
|
||||
type="primary"
|
||||
size="small"
|
||||
theme="ai"
|
||||
class="w-1/2"
|
||||
:disabled="
|
||||
aiStep !== AI_STEP.MODIFY ||
|
||||
finalSchema?.tables?.length === 0 ||
|
||||
(aiLoading && callFunction === 'onCreateSchema') ||
|
||||
isOldPromptChanged
|
||||
"
|
||||
:loading="aiLoading && callFunction === 'onCreateSchema'"
|
||||
@click="onCreateSchema"
|
||||
>
|
||||
{{ $t('activity.createProject') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
@@ -501,8 +550,10 @@ onMounted(() => {
|
||||
<!-- create base preview panel -->
|
||||
|
||||
<template v-if="aiStep === AI_STEP.LOADING || aiStep === AI_STEP.PROMPT">
|
||||
<div v-if="aiStep === AI_STEP.LOADING"
|
||||
class="text-sm font-bold text-nc-content-purple-dark dark:text-nc-content-purple-medium">
|
||||
<div
|
||||
v-if="aiStep === AI_STEP.LOADING"
|
||||
class="text-sm font-bold text-nc-content-purple-dark dark:text-nc-content-purple-medium"
|
||||
>
|
||||
{{ $t('title.generatingBaseTailoredToYourRequirement') }}
|
||||
</div>
|
||||
<div v-else class="text-sm font-bold text-nc-content-purple-dark dark:text-nc-content-purple-medium">
|
||||
@@ -510,23 +561,34 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<template v-if="aiStep === AI_STEP.LOADING">
|
||||
<div v-for="(loadingText, idx) of activeLoadingText" :key="idx"
|
||||
class="text-sm text-nc-content-purple-light flex items-center">
|
||||
<div
|
||||
v-for="(loadingText, idx) of activeLoadingText"
|
||||
:key="idx"
|
||||
class="text-sm text-nc-content-purple-light flex items-center"
|
||||
>
|
||||
{{ loadingText }}
|
||||
<div v-if="loadingText.length === loadingMessages[idx]?.length" class="nc-animate-dots"></div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border-1 border-nc-border-purple-light">
|
||||
<div v-for="idx in 7" :key="idx"
|
||||
class="px-3 py-2 flex items-center gap-2 border-b-1 border-nc-border-purple-light !last-of-type:border-b-0">
|
||||
<div
|
||||
v-for="idx in 7"
|
||||
:key="idx"
|
||||
class="px-3 py-2 flex items-center gap-2 border-b-1 border-nc-border-purple-light !last-of-type:border-b-0"
|
||||
>
|
||||
<div class="flex-1 flex items-center gap-2">
|
||||
<a-skeleton-input :active="aiStep === AI_STEP.LOADING"
|
||||
class="!w-4 !h-4 !rounded overflow-hidden !bg-nc-bg-purple-light" />
|
||||
<a-skeleton-input :active="aiStep === AI_STEP.LOADING"
|
||||
class="!h-4 !rounded overflow-hidden !bg-nc-bg-purple-light" :class="{
|
||||
<a-skeleton-input
|
||||
:active="aiStep === AI_STEP.LOADING"
|
||||
class="!w-4 !h-4 !rounded overflow-hidden !bg-nc-bg-purple-light"
|
||||
/>
|
||||
<a-skeleton-input
|
||||
:active="aiStep === AI_STEP.LOADING"
|
||||
class="!h-4 !rounded overflow-hidden !bg-nc-bg-purple-light"
|
||||
:class="{
|
||||
'!w-[133px]': idx % 2 === 0,
|
||||
'!w-[90px]': idx % 2 !== 0,
|
||||
}" />
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid place-items-center h-6 w-6">
|
||||
<GeneralIcon icon="arrowDown" class="text-nc-content-purple-light" />
|
||||
@@ -544,19 +606,29 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<template v-if="predictedSchema?.tables">
|
||||
<AiWizardCard v-model:active-tab="activePreviewTab" :tabs="previewTabs"
|
||||
class="!rounded-xl flex-1 flex flex-col min-w-[320px]" content-class-name="flex-1 flex flex-col">
|
||||
<AiWizardCard
|
||||
v-model:active-tab="activePreviewTab"
|
||||
:tabs="previewTabs"
|
||||
class="!rounded-xl flex-1 flex flex-col min-w-[320px]"
|
||||
content-class-name="flex-1 flex flex-col"
|
||||
>
|
||||
<template #tabContent>
|
||||
<a-collapse v-if="activePreviewTab === SchemaPreviewTabs.TABLES_AND_VIEWS"
|
||||
v-model:active-key="previewExpansionPanel" class="nc-schema-preview-table flex flex-col">
|
||||
<a-collapse
|
||||
v-if="activePreviewTab === SchemaPreviewTabs.TABLES_AND_VIEWS"
|
||||
v-model:active-key="previewExpansionPanel"
|
||||
class="nc-schema-preview-table flex flex-col"
|
||||
>
|
||||
<template #expandIcon> </template>
|
||||
|
||||
<a-collapse-panel v-for="table in predictedSchema.tables" :key="table.title" collapsible="disabled">
|
||||
<template #header>
|
||||
<div class="w-full flex items-center px-4 py-2"
|
||||
@click="handleUpdatePreviewExpansionPanel(table.title, !viewsGrouped[table.title]?.length)">
|
||||
<div
|
||||
class="w-full flex items-center px-4 py-2"
|
||||
@click="handleUpdatePreviewExpansionPanel(table.title, !viewsGrouped[table.title]?.length)"
|
||||
>
|
||||
<div
|
||||
class="flex-1 flex items-center gap-3 text-nc-content-purple-dark dark:text-nc-content-purple-medium">
|
||||
class="flex-1 flex items-center gap-3 text-nc-content-purple-dark dark:text-nc-content-purple-medium"
|
||||
>
|
||||
<NcCheckbox :checked="!table.excluded" theme="ai" @click.stop="onExcludeTable(table)" />
|
||||
|
||||
<GeneralIcon icon="table" class="flex-none !h-4 opacity-85" />
|
||||
@@ -568,13 +640,24 @@ onMounted(() => {
|
||||
{{ table.title }}
|
||||
</NcTooltip>
|
||||
</div>
|
||||
<NcButton size="xs" type="text" theme="ai" icon-only class="!px-0 !h-6 !w-6 !min-w-6" :class="{
|
||||
hidden: !viewsGrouped[table.title]?.length,
|
||||
}">
|
||||
<NcButton
|
||||
size="xs"
|
||||
type="text"
|
||||
theme="ai"
|
||||
icon-only
|
||||
class="!px-0 !h-6 !w-6 !min-w-6"
|
||||
:class="{
|
||||
hidden: !viewsGrouped[table.title]?.length,
|
||||
}"
|
||||
>
|
||||
<template #icon>
|
||||
<GeneralIcon icon="arrowDown" class="transform transition-all opacity-80" :class="{
|
||||
'rotate-180': previewExpansionPanel.includes(table.title),
|
||||
}" />
|
||||
<GeneralIcon
|
||||
icon="arrowDown"
|
||||
class="transform transition-all opacity-80"
|
||||
:class="{
|
||||
'rotate-180': previewExpansionPanel.includes(table.title),
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,6 @@ const emit = defineEmits(['update:visible', 'installed'])
|
||||
|
||||
const visible = useVModel(props, 'visible', emit)
|
||||
|
||||
|
||||
const { $api } = useNuxtApp()
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -125,7 +124,7 @@ watch(
|
||||
<GeneralIcon icon="ncBox" class="h-5 w-5" />
|
||||
<div class="flex-1 text-bodyLgBold">{{ t('labels.appMarket') }}</div>
|
||||
|
||||
<NcButton size="small" type="text" @click="visible = false" class="self-start">
|
||||
<NcButton size="small" type="text" class="self-start" @click="visible = false">
|
||||
<GeneralIcon icon="close" class="text-nc-content-gray-subtle2" />
|
||||
</NcButton>
|
||||
</div>
|
||||
@@ -133,15 +132,18 @@ watch(
|
||||
<div class="flex flex-col gap-4 h-[600px] p-6">
|
||||
<!-- Search and Filter Bar -->
|
||||
<div class="flex gap-3">
|
||||
<a-input v-model:value="searchQuery" class="flex-1 nc-input-sm nc-input-shadow !rounded-md"
|
||||
:placeholder="t('placeholder.searchByTitle')" allow-clear>
|
||||
<a-input
|
||||
v-model:value="searchQuery"
|
||||
class="flex-1 nc-input-sm nc-input-shadow !rounded-md"
|
||||
:placeholder="t('placeholder.searchByTitle')"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
<GeneralIcon icon="search" class="h-4 w-4 text-nc-content-gray-muted" />
|
||||
</template>
|
||||
</a-input>
|
||||
|
||||
<NcSelect v-model:value="selectedCategory" class="w-48 nc-select-sm" :placeholder="t('labels.category')"
|
||||
allow-clear>
|
||||
<NcSelect v-model:value="selectedCategory" class="w-48 nc-select-sm" :placeholder="t('labels.category')" allow-clear>
|
||||
<a-select-option v-for="cat in categories" :key="cat" :value="cat">
|
||||
{{ cat }}
|
||||
</a-select-option>
|
||||
@@ -162,9 +164,12 @@ watch(
|
||||
</div>
|
||||
|
||||
<div v-else class="flex flex-col gap-3 overflow-y-auto pr-2">
|
||||
<div v-for="sandbox in filteredSandboxes" :key="sandbox.id"
|
||||
<div
|
||||
v-for="sandbox in filteredSandboxes"
|
||||
:key="sandbox.id"
|
||||
class="nc-sandbox-card border-1 border-nc-border-gray-medium rounded-lg p-4 hover:shadow-md transition-all cursor-pointer"
|
||||
@click="installSandbox(sandbox)">
|
||||
@click="installSandbox(sandbox)"
|
||||
>
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-start justify-between gap-3 mb-2">
|
||||
@@ -172,8 +177,13 @@ watch(
|
||||
<h3 class="text-base font-semibold text-nc-content-gray mb-1">{{ sandbox.title }}</h3>
|
||||
<p class="text-sm text-nc-content-gray-subtle line-clamp-2">{{ sandbox.description }}</p>
|
||||
</div>
|
||||
<NcButton :loading="installing === sandbox.id" :disabled="!!installing" size="small" type="primary"
|
||||
@click.stop="installSandbox(sandbox)">
|
||||
<NcButton
|
||||
:loading="installing === sandbox.id"
|
||||
:disabled="!!installing"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click.stop="installSandbox(sandbox)"
|
||||
>
|
||||
<template #icon>
|
||||
<GeneralIcon icon="download" />
|
||||
</template>
|
||||
|
||||
@@ -3,8 +3,8 @@ import { FormBuilderValidatorType } from 'nocodb-sdk'
|
||||
import { FORM_BUILDER_NON_CATEGORIZED, FormBuilderInputType } from '#imports'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
baseId?: string
|
||||
visible: boolean
|
||||
baseId?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:visible'])
|
||||
@@ -16,160 +16,158 @@ const { $api } = useNuxtApp()
|
||||
const { t } = useI18n()
|
||||
|
||||
const initialSanboxFormState = ref<Record<string, any>>({
|
||||
title: '',
|
||||
description: '',
|
||||
category: '',
|
||||
visibility: 'private',
|
||||
title: '',
|
||||
description: '',
|
||||
category: '',
|
||||
visibility: 'private',
|
||||
})
|
||||
|
||||
const workspaceStore = useWorkspace()
|
||||
|
||||
|
||||
const { activeWorkspaceId } = storeToRefs(workspaceStore)
|
||||
const { base } = storeToRefs(useBase())
|
||||
|
||||
const basesStore = useBases()
|
||||
|
||||
const convertToSandbox = async (formState: Record<string, any>) => {
|
||||
try {
|
||||
const response = await $api.internal.postOperation(
|
||||
activeWorkspaceId.value as string,
|
||||
props.baseId,
|
||||
{
|
||||
operation: 'sandboxCreate',
|
||||
} as any,
|
||||
{
|
||||
title: formState.title,
|
||||
description: formState.description,
|
||||
category: formState.category,
|
||||
visibility: formState.visibility,
|
||||
},
|
||||
)
|
||||
try {
|
||||
const response = await $api.internal.postOperation(
|
||||
activeWorkspaceId.value as string,
|
||||
props.baseId,
|
||||
{
|
||||
operation: 'sandboxCreate',
|
||||
} as any,
|
||||
{
|
||||
title: formState.title,
|
||||
description: formState.description,
|
||||
category: formState.category,
|
||||
visibility: formState.visibility,
|
||||
},
|
||||
)
|
||||
|
||||
message.success(t('msg.success.sandboxCreated'))
|
||||
visible.value = false
|
||||
message.success(t('msg.success.sandboxCreated'))
|
||||
visible.value = false
|
||||
|
||||
// Update the base with the sandbox_id from response
|
||||
if (response && response.sandbox_id) {
|
||||
const currentBase = basesStore.bases.get(props.baseId)
|
||||
if (currentBase) {
|
||||
; (currentBase as any).sandbox_id = response.sandbox_id
|
||||
}
|
||||
}
|
||||
|
||||
// Reload base to ensure all sandbox data is loaded
|
||||
await basesStore.loadProject(props.baseId, true)
|
||||
} catch (e: any) {
|
||||
message.error(await extractSdkResponseErrorMsg(e))
|
||||
// Update the base with the sandbox_id from response
|
||||
if (response && response.sandbox_id) {
|
||||
const currentBase = basesStore.bases.get(props.baseId)
|
||||
if (currentBase) {
|
||||
;(currentBase as any).sandbox_id = response.sandbox_id
|
||||
}
|
||||
}
|
||||
|
||||
// Reload base to ensure all sandbox data is loaded
|
||||
await basesStore.loadProject(props.baseId, true)
|
||||
} catch (e: any) {
|
||||
message.error(await extractSdkResponseErrorMsg(e))
|
||||
}
|
||||
}
|
||||
|
||||
const { formState, isLoading, submit } = useProvideFormBuilderHelper({
|
||||
formSchema: [
|
||||
formSchema: [
|
||||
{
|
||||
type: FormBuilderInputType.Input,
|
||||
label: t('labels.sandboxTitle'),
|
||||
span: 24,
|
||||
model: 'title',
|
||||
placeholder: 'Enter a descriptive title',
|
||||
category: FORM_BUILDER_NON_CATEGORIZED,
|
||||
validators: [
|
||||
{
|
||||
type: FormBuilderInputType.Input,
|
||||
label: t('labels.sandboxTitle'),
|
||||
span: 24,
|
||||
model: 'title',
|
||||
placeholder: 'Enter a descriptive title',
|
||||
category: FORM_BUILDER_NON_CATEGORIZED,
|
||||
validators: [
|
||||
{
|
||||
type: FormBuilderValidatorType.Required,
|
||||
message: t('labels.titleRequired'),
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
type: FormBuilderValidatorType.Required,
|
||||
message: t('labels.titleRequired'),
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Textarea,
|
||||
label: t('labels.sandboxDescription'),
|
||||
span: 24,
|
||||
model: 'description',
|
||||
placeholder: "Describe your application's capabilities",
|
||||
category: FORM_BUILDER_NON_CATEGORIZED,
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Input,
|
||||
label: t('labels.sandboxCategory'),
|
||||
span: 12,
|
||||
model: 'category',
|
||||
placeholder: 'e.g., CRM, HR',
|
||||
category: FORM_BUILDER_NON_CATEGORIZED,
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Select,
|
||||
label: t('labels.sandboxVisibility'),
|
||||
span: 12,
|
||||
model: 'visibility',
|
||||
category: FORM_BUILDER_NON_CATEGORIZED,
|
||||
options: [
|
||||
{ label: 'Public', value: 'public', icon: 'eye' },
|
||||
{ label: 'Private', value: 'private', icon: 'lock' },
|
||||
{ label: 'Unlisted', value: 'unlisted', icon: 'ncEyeOff' },
|
||||
],
|
||||
defaultValue: 'private',
|
||||
},
|
||||
],
|
||||
onSubmit: async () => {
|
||||
return await convertToSandbox(formState.value)
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
initialState: initialSanboxFormState,
|
||||
{
|
||||
type: FormBuilderInputType.Textarea,
|
||||
label: t('labels.sandboxDescription'),
|
||||
span: 24,
|
||||
model: 'description',
|
||||
placeholder: "Describe your application's capabilities",
|
||||
category: FORM_BUILDER_NON_CATEGORIZED,
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Input,
|
||||
label: t('labels.sandboxCategory'),
|
||||
span: 12,
|
||||
model: 'category',
|
||||
placeholder: 'e.g., CRM, HR',
|
||||
category: FORM_BUILDER_NON_CATEGORIZED,
|
||||
},
|
||||
{
|
||||
type: FormBuilderInputType.Select,
|
||||
label: t('labels.sandboxVisibility'),
|
||||
span: 12,
|
||||
model: 'visibility',
|
||||
category: FORM_BUILDER_NON_CATEGORIZED,
|
||||
options: [
|
||||
{ label: 'Public', value: 'public', icon: 'eye' },
|
||||
{ label: 'Private', value: 'private', icon: 'lock' },
|
||||
{ label: 'Unlisted', value: 'unlisted', icon: 'ncEyeOff' },
|
||||
],
|
||||
defaultValue: 'private',
|
||||
},
|
||||
],
|
||||
onSubmit: async () => {
|
||||
return await convertToSandbox(formState.value)
|
||||
},
|
||||
initialState: initialSanboxFormState,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="p-4 w-full flex items-center gap-3 border-b border-nc-border-gray-medium">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg bg-gradient-to-br from-brand-500 to-brand-600 flex items-center justify-center">
|
||||
<GeneralIcon icon="ncBox" class="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold text-lg text-nc-content-gray-emphasis">
|
||||
Create Managed App
|
||||
</div>
|
||||
<div class="text-xs text-nc-content-gray-subtle2">{{ $t('labels.publishToAppStore') }}</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="p-4 w-full flex items-center gap-3 border-b border-nc-border-gray-medium">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-brand-500 to-brand-600 flex items-center justify-center">
|
||||
<GeneralIcon icon="ncBox" class="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold text-lg text-nc-content-gray-emphasis">Create Managed App</div>
|
||||
<div class="text-xs text-nc-content-gray-subtle2">{{ $t('labels.publishToAppStore') }}</div>
|
||||
</div>
|
||||
|
||||
<NcButton size="small" type="text" @click="visible = false" class="self-start">
|
||||
<GeneralIcon icon="close" class="text-nc-content-gray-subtle2" />
|
||||
</NcButton>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6 nc-scrollbar-thin">
|
||||
<NcFormBuilder>
|
||||
<template #header>
|
||||
<NcAlert type="info" :align="'top'"
|
||||
description="Create managed application that can be published to the App Store. You'll be able to manage versions and push updates to all installations."
|
||||
class="!p-3 !items-start bg-nc-bg-blue-light border-1 !border-nc-blue-200 rounded-lg p-3 mb-4">
|
||||
<template #icon>
|
||||
<GeneralIcon icon="info" class="w-4 h-4 mt-0.5 text-nc-content-blue-dark flex-none" />
|
||||
</template>
|
||||
</NcAlert>
|
||||
</template>
|
||||
</NcFormBuilder>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 px-6 py-3 border-t border-nc-border-gray-medium">
|
||||
<NcButton size="small" type="secondary" :disabled="isLoading" @click="visible = false">
|
||||
{{ $t('general.cancel') }}
|
||||
</NcButton>
|
||||
<NcButton size="small" type="primary" :loading="isLoading" @click="submit">
|
||||
<template #icon>
|
||||
<GeneralIcon icon="ncBox" />
|
||||
</template>
|
||||
Convert to sandbox
|
||||
</NcButton>
|
||||
</div>
|
||||
<NcButton size="small" type="text" class="self-start" @click="visible = false">
|
||||
<GeneralIcon icon="close" class="text-nc-content-gray-subtle2" />
|
||||
</NcButton>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6 nc-scrollbar-thin">
|
||||
<NcFormBuilder>
|
||||
<template #header>
|
||||
<NcAlert
|
||||
type="info"
|
||||
align="top"
|
||||
description="Create managed application that can be published to the App Store. You'll be able to manage versions and push updates to all installations."
|
||||
class="!p-3 !items-start bg-nc-bg-blue-light border-1 !border-nc-blue-200 rounded-lg p-3 mb-4"
|
||||
>
|
||||
<template #icon>
|
||||
<GeneralIcon icon="info" class="w-4 h-4 mt-0.5 text-nc-content-blue-dark flex-none" />
|
||||
</template>
|
||||
</NcAlert>
|
||||
</template>
|
||||
</NcFormBuilder>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 px-6 py-3 border-t border-nc-border-gray-medium">
|
||||
<NcButton size="small" type="secondary" :disabled="isLoading" @click="visible = false">
|
||||
{{ $t('general.cancel') }}
|
||||
</NcButton>
|
||||
<NcButton size="small" type="primary" :loading="isLoading" @click="submit">
|
||||
<template #icon>
|
||||
<GeneralIcon icon="ncBox" />
|
||||
</template>
|
||||
Convert to sandbox
|
||||
</NcButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.nc-modal-convert-to-sandbox {
|
||||
.nc-modal {
|
||||
max-height: min(90vh, 540px) !important;
|
||||
height: min(90vh, 540px) !important;
|
||||
}
|
||||
.nc-modal {
|
||||
max-height: min(90vh, 540px) !important;
|
||||
height: min(90vh, 540px) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
interface Props {
|
||||
visible: boolean
|
||||
variant: 'modal' | 'dropdown'
|
||||
baseCreateMode: NcBaseCreateMode | null
|
||||
visible: boolean
|
||||
variant: 'modal' | 'dropdown'
|
||||
baseCreateMode: NcBaseCreateMode | null
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
variant: 'dropdown',
|
||||
variant: 'dropdown',
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'update:baseCreateMode', value: NcBaseCreateMode | null): void
|
||||
(e: 'onSelect', mode: NcBaseCreateMode): void
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'update:baseCreateMode', value: NcBaseCreateMode | null): void
|
||||
(e: 'onSelect', mode: NcBaseCreateMode): void
|
||||
}>()
|
||||
|
||||
|
||||
const vVisible = useVModel(props, 'visible', emits)
|
||||
|
||||
const baseCreateMode = useVModel(props, 'baseCreateMode', emits)
|
||||
@@ -29,42 +28,58 @@ const { isTemplatesFeatureEnabled } = storeToRefs(workspaceStore)
|
||||
const { isAiFeaturesEnabled } = useNocoAi()
|
||||
|
||||
const onClickOption = (mode: NcBaseCreateMode) => {
|
||||
if (isTemplatesFeatureEnabled.value && mode === NcBaseCreateMode.FROM_TEMPLATE) {
|
||||
vVisible.value = false
|
||||
navigateToTemplates()
|
||||
if (isTemplatesFeatureEnabled.value && mode === NcBaseCreateMode.FROM_TEMPLATE) {
|
||||
vVisible.value = false
|
||||
navigateToTemplates()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
baseCreateMode.value = mode
|
||||
return
|
||||
}
|
||||
|
||||
baseCreateMode.value = mode
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!isAiFeaturesEnabled.value && props.variant === 'modal') {
|
||||
baseCreateMode.value = NcBaseCreateMode.FROM_SCRATCH
|
||||
}
|
||||
if (!isAiFeaturesEnabled.value && props.variant === 'modal') {
|
||||
baseCreateMode.value = NcBaseCreateMode.FROM_SCRATCH
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcMenu v-if="variant === 'dropdown'" variant="large" data-testid="nc-home-create-new-menu"
|
||||
@click="vVisible = false">
|
||||
<DashboardTreeViewCreateProjectMenuItem v-e="['c:base:create:scratch']" icon="plus" label="From Scratch"
|
||||
subtext="Start with an empty base" @click="onClickOption(NcBaseCreateMode.FROM_SCRATCH)" />
|
||||
<NcMenu v-if="variant === 'dropdown'" variant="large" data-testid="nc-home-create-new-menu" @click="vVisible = false">
|
||||
<DashboardTreeViewCreateProjectMenuItem
|
||||
v-e="['c:base:create:scratch']"
|
||||
icon="plus"
|
||||
label="From Scratch"
|
||||
subtext="Start with an empty base"
|
||||
@click="onClickOption(NcBaseCreateMode.FROM_SCRATCH)"
|
||||
/>
|
||||
|
||||
<DashboardTreeViewCreateProjectMenuItem v-if="isAiFeaturesEnabled" v-e="['c:base:ai:create']"
|
||||
icon="ncAutoAwesome" label="Build with AI" subtext="Pre-built structures for common use cases"
|
||||
@click="onClickOption(NcBaseCreateMode.BUILD_WITH_AI)" />
|
||||
<DashboardTreeViewCreateProjectMenuItem
|
||||
v-if="isAiFeaturesEnabled"
|
||||
v-e="['c:base:ai:create']"
|
||||
icon="ncAutoAwesome"
|
||||
label="Build with AI"
|
||||
subtext="Pre-built structures for common use cases"
|
||||
@click="onClickOption(NcBaseCreateMode.BUILD_WITH_AI)"
|
||||
/>
|
||||
</NcMenu>
|
||||
<div v-else class="flex flex-row gap-6 flex-wrap max-w-[min(80vw,738px)] children:(!w-[230px] !max-w-[230px])">
|
||||
<ProjectActionItem
|
||||
v-e="['c:base:create:scratch']"
|
||||
icon="plus"
|
||||
label="From Scratch"
|
||||
subtext="Start with an empty base"
|
||||
@click="onClickOption(NcBaseCreateMode.FROM_SCRATCH)"
|
||||
/>
|
||||
|
||||
</NcMenu>
|
||||
<div v-else class="flex flex-row gap-6 flex-wrap max-w-[min(80vw,738px)] children:(!w-[230px] !max-w-[230px])">
|
||||
<ProjectActionItem v-e="['c:base:create:scratch']" icon="plus" label="From Scratch"
|
||||
subtext="Start with an empty base" @click="onClickOption(NcBaseCreateMode.FROM_SCRATCH)" />
|
||||
|
||||
<ProjectActionItem v-if="isAiFeaturesEnabled" v-e="['c:base:ai:create']" icon="ncAutoAwesome"
|
||||
label="Build with AI" subtext="Pre-built structures for common use cases"
|
||||
@click="onClickOption(NcBaseCreateMode.BUILD_WITH_AI)" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<ProjectActionItem
|
||||
v-if="isAiFeaturesEnabled"
|
||||
v-e="['c:base:ai:create']"
|
||||
icon="ncAutoAwesome"
|
||||
label="Build with AI"
|
||||
subtext="Pre-built structures for common use cases"
|
||||
@click="onClickOption(NcBaseCreateMode.BUILD_WITH_AI)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,57 +1,56 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import { NcMenuItem } from "#components"
|
||||
import { NcMenuItem } from '#components'
|
||||
|
||||
interface Props {
|
||||
label: string
|
||||
subtext?: string
|
||||
icon?: IconMapKey
|
||||
variant?: 'dropdown' | 'modal'
|
||||
label: string
|
||||
subtext?: string
|
||||
icon?: IconMapKey
|
||||
variant?: 'dropdown' | 'modal'
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
|
||||
withDefaults(defineProps<Props>(), {})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="variant === 'modal' ? 'div' : NcMenuItem"
|
||||
:inner-class="`w-full ${$slots.subtext || subtext ? '!items-start' : ''}`"
|
||||
:class="`nc-create-project-menu-item-${variant}`">
|
||||
<div class="nc-icon-wrapper">
|
||||
<slot name="icon">
|
||||
<GeneralIcon v-if="icon" :icon="icon" class="h-4 w-4 flex-none" />
|
||||
</slot>
|
||||
</div>
|
||||
<component
|
||||
:is="variant === 'modal' ? 'div' : NcMenuItem"
|
||||
:inner-class="`w-full ${$slots.subtext || subtext ? '!items-start' : ''}`"
|
||||
:class="`nc-create-project-menu-item-${variant}`"
|
||||
>
|
||||
<div class="nc-icon-wrapper">
|
||||
<slot name="icon">
|
||||
<GeneralIcon v-if="icon" :icon="icon" class="h-4 w-4 flex-none" />
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="nc-content-wrapper">
|
||||
<div class="nc-content-label">
|
||||
<slot name="label">{{ label }}</slot>
|
||||
</div>
|
||||
<div v-if="$slots.subtext || subtext" class="nc-content-subtext">
|
||||
<slot name="subtext">{{ subtext }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
<div class="nc-content-wrapper">
|
||||
<div class="nc-content-label">
|
||||
<slot name="label">{{ label }}</slot>
|
||||
</div>
|
||||
<div v-if="$slots.subtext || subtext" class="nc-content-subtext">
|
||||
<slot name="subtext">{{ subtext }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nc-icon-wrapper {
|
||||
@apply flex items-center justify-center h-5 children:flex-none;
|
||||
@apply flex items-center justify-center h-5 children:flex-none;
|
||||
}
|
||||
|
||||
.nc-create-project-menu-item-modal {
|
||||
@apply font-normal text-sm flex items-start gap-3 mx-1 px-2 py-1 rounded-md hover:bg-nc-bg-gray-light transition-colors cursor-pointer;
|
||||
|
||||
.nc-content-wrapper {
|
||||
.nc-content-label {}
|
||||
@apply font-normal text-sm flex items-start gap-3 mx-1 px-2 py-1 rounded-md hover:bg-nc-bg-gray-light transition-colors cursor-pointer;
|
||||
|
||||
.nc-content-wrapper {
|
||||
.nc-content-label {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nc-content-wrapper {
|
||||
.nc-content-subtext {
|
||||
@apply text-tiny !leading-4 text-nc-content-gray-muted;
|
||||
}
|
||||
.nc-content-subtext {
|
||||
@apply text-tiny !leading-4 text-nc-content-gray-muted;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user