feat: full-code app builder reachable from home in preview

This commit is contained in:
Ruben Fiszel
2025-12-23 09:11:35 +00:00
parent d0343cc0e9
commit 1771235ebb
3 changed files with 197 additions and 27 deletions
@@ -1,17 +1,36 @@
<script lang="ts">
import { createBubbler, stopPropagation } from 'svelte/legacy'
const bubble = createBubbler()
import { createEventDispatcher } from 'svelte'
import { fade } from 'svelte/transition'
import Button from '../button/Button.svelte'
import { twMerge } from 'tailwind-merge'
import CloseButton from '../CloseButton.svelte'
export let title: string
export let open: boolean = false
let c: string = ''
export { c as class }
export let style = ''
export let cancelText: string | undefined = undefined
export let kind: 'button' | 'X' = 'button'
interface Props {
title: string
open?: boolean
class?: string
style?: string
cancelText?: string | undefined
kind?: 'button' | 'X'
settings?: import('svelte').Snippet
children?: import('svelte').Snippet
actions?: import('svelte').Snippet
}
let {
title,
open = $bindable(false),
class: c = '',
style = '',
cancelText = undefined,
kind = 'button',
settings,
children,
actions
}: Props = $props()
const dispatch = createEventDispatcher()
@@ -37,13 +56,13 @@
}
</script>
<svelte:window on:keydown|capture={onKeyDown} />
<svelte:window onkeydowncapture={onKeyDown} />
{#if open}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
on:click={() => (open = false)}
onclick={() => (open = false)}
transition:fadeFast|local
class={'fixed top-0 bottom-0 left-0 right-0 z-[9999]'}
role="dialog"
@@ -58,9 +77,9 @@
<div class="fixed inset-0 z-10 overflow-y-auto">
<div class="flex min-h-full items-center justify-center p-4">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
on:click|stopPropagation
onclick={stopPropagation(bubble('click'))}
class={twMerge(
'relative transform overflow-hidden rounded-lg bg-surface px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6',
c,
@@ -78,17 +97,17 @@
<div class="ml-4 text-left flex-1">
<div class="flex flex-row items-center justify-between">
<h3>{title}</h3>
<slot name="settings" />
{@render settings?.()}
</div>
<div class="mt-4 text-sm text-primary">
<slot />
{@render children?.()}
</div>
</div>
</div>
{#if kind == 'button'}
<div class="flex items-center space-x-2 flex-row-reverse space-x-reverse mt-4">
<slot name="actions" />
{@render actions?.()}
<Button
on:click={() => {
dispatch('canceled')
@@ -5,21 +5,47 @@
import { Button } from '$lib/components/common'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import { LayoutDashboard, Loader2, Plus } from 'lucide-svelte'
import Modal from '$lib/components/common/modal/Modal.svelte'
import { LayoutDashboard, Loader2, Plus, Code2, FlaskConical } from 'lucide-svelte'
import { importStore } from '../apps/store'
import Badge from '$lib/components/common/badge/Badge.svelte'
import YAML from 'yaml'
import Tooltip from '../Tooltip.svelte'
let drawer: Drawer | undefined = undefined
let pendingRaw: string = ''
let importType: 'yaml' | 'json' = 'yaml'
// Modal states
let appTypeModalOpen = $state(false)
let featurePreviewModalOpen = $state(false)
async function importRaw() {
$importStore = importType === 'yaml' ? YAML.parse(pendingRaw) : JSON.parse(pendingRaw)
await goto('/apps/add?nodraft=true')
drawer?.closeDrawer?.()
}
function openAppTypeModal() {
appTypeModalOpen = true
}
function selectLowCode() {
appTypeModalOpen = false
goto(`${base}/apps/add?nodraft=true`)
}
function selectFullCode() {
appTypeModalOpen = false
featurePreviewModalOpen = true
}
function confirmFullCode() {
featurePreviewModalOpen = false
goto(`${base}/apps_raw/add?nodraft=true`)
}
</script>
<!-- Buttons -->
@@ -27,11 +53,11 @@
<Button
id="create-app-button"
aiId="apps-create-actions-app"
aiDescription="Create a new low-code app"
aiDescription="Create a new app"
unifiedSize="lg"
startIcon={{ icon: Plus }}
endIcon={{ icon: LayoutDashboard }}
href="{base}/apps/add?nodraft=true"
on:click={openAppTypeModal}
variant="accent"
dropdownItems={[
{
@@ -48,16 +74,101 @@
importType = 'json'
}
}
// {
// label: 'Build app in React/Vue/Svelte (alpha)',
// onClick: () => goto('/apps_raw/add?nodraft=true')
// }
]}
>
<div class="flex flex-row items-center"> App </div>
</Button>
</div>
<!-- App Type Selection Modal -->
<Modal bind:open={appTypeModalOpen} title="Choose your app builder">
<div class="flex flex-col gap-4 pr-4">
<div class="grid grid-cols-2 gap-8">
<!-- Low-code option -->
<button
class="flex flex-col items-center gap-3 p-6 rounded-lg border-2 border-gray-200 dark:border-gray-700 hover:border-orange-500 dark:hover:border-orange-400 hover:bg-orange-50 dark:hover:bg-orange-900/20 transition-all cursor-pointer group"
onclick={selectLowCode}
>
<div
class="w-32 h-32 rounded-xl bg-orange-100 dark:bg-orange-900/40 flex items-center justify-center group-hover:bg-orange-200 dark:group-hover:bg-orange-800/50 transition-colors"
>
<LayoutDashboard size={32} class="text-orange-600 dark:text-orange-400" />
</div>
<div class="text-center">
<h3 class="font-semibold text-primary">Low-code App</h3>
<p class="text-xs text-tertiary mt-1">
Drag-and-drop UI builder with 60+ powerful components.
<br /><br />
Better for simple apps or apps that require minimal customization.
</p>
</div>
</button>
<button
class="relative flex flex-col items-center gap-3 p-6 rounded-lg border-2 border-gray-200 dark:border-gray-700 hover:border-purple-500 dark:hover:border-purple-400 hover:bg-purple-50 dark:hover:bg-purple-900/20 transition-all cursor-pointer group"
onclick={selectFullCode}
>
<div
class="w-32 h-32 rounded-xl bg-purple-100 dark:bg-purple-900/40 flex items-center justify-center group-hover:bg-purple-200 dark:group-hover:bg-purple-800/50 transition-colors"
>
<Code2 size={32} class="text-purple-600 dark:text-purple-400" />
</div>
<div class="text-center">
<h3 class="font-semibold text-primary">Full-code App</h3>
<p class="text-xs text-tertiary mt-1">
Build with React or Svelte with full control and a powerful AI agent.
</p>
</div>
<br /><br />
<div class="flex flex-row items-center gap-2">
<Badge color="yellow">
<FlaskConical size={12} class="mr-1" />
Preview (unstable)
</Badge>
</div>
</button>
</div>
</div>
</Modal>
<!-- Feature Preview Warning Modal -->
<Modal kind="X" bind:open={featurePreviewModalOpen} title="Feature Preview Notice">
<div class="flex flex-col gap-4">
<div
class="flex items-start gap-3 p-4 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800"
>
<FlaskConical size={24} class="text-yellow-600 dark:text-yellow-400 shrink-0 mt-0.5" />
<div class="text-sm text-yellow-800 dark:text-yellow-200">
<p class="font-semibold mb-2">Full-code Apps are in Feature Preview</p>
<p>
This feature is not yet stable and may change significantly before it reaches General
Availability (GA), which is expected in <strong>January 2026</strong>.
</p>
<ul class="list-disc list-inside mt-2 space-y-1 text-yellow-700 dark:text-yellow-300">
<li>APIs and interfaces may change without notice</li>
<li>Some functionality may be incomplete or buggy</li>
<li>Not recommended for production use yet</li>
</ul>
</div>
</div>
<div class="flex justify-end gap-3 pt-2">
<Button
color="light"
size="sm"
on:click={() => {
featurePreviewModalOpen = false
appTypeModalOpen = true
}}
>
Go back
</Button>
<Button color="blue" size="sm" on:click={confirmFullCode}>I understand, continue</Button>
</div>
</div>
</Modal>
<!-- Raw JSON -->
<Drawer bind:this={drawer} size="800px">
<DrawerContent
@@ -24,7 +24,15 @@
import Select from '$lib/components/select/Select.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import { AlertTriangle, Sparkles, ArrowRight, Plus, List, Ban } from 'lucide-svelte'
import {
AlertTriangle,
Sparkles,
ArrowRight,
Plus,
List,
Ban,
ExternalLinkIcon
} from 'lucide-svelte'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import RawAppDataTableList from '$lib/components/raw_apps/RawAppDataTableList.svelte'
@@ -398,8 +406,24 @@
<AlertTriangle size={16} class="text-yellow-600 dark:text-yellow-400 shrink-0" />
<div class="text-sm text-yellow-800 dark:text-yellow-200">
<span class="font-medium">No datatables configured.</span>
You can still create an app, but AI won't be able to create database tables. Configure
datatables in workspace settings to enable this feature.
You can still create an app, but for data storage you won't be able to use data tables
which are <b>HIGHLY RECOMMENDED</b>.
<br />
{#if $userStore?.is_admin}
Configure datatables in
<a
href="/workspace/settings?tab=windmill_data_tables"
target="_blank"
class="inline-flex items-center gap-1"
>workspace settings <ExternalLinkIcon size={16} />
</a> to enable this feature.
{:else}
<b>
Ask your workspace admin to configure datatables in workspace settings to enable
this feature.
</b>
{/if}
</div>
</div>
{:else}
@@ -509,7 +533,23 @@
>
<AlertTriangle size={16} class="text-gray-500 shrink-0" />
<div class="text-sm text-tertiary">
AI is not configured for this workspace. You can still create an app manually.
AI is not configured for this workspace. You can still create an app manually but <b
>using AI is highly recommended</b
>.
<br />
{#if $userStore?.is_admin}
Configure AI in
<a
href="/workspace/settings?tab=ai"
target="_blank"
class="inline-flex items-center gap-1"
>workspace settings <ExternalLinkIcon size={16} />
</a>
to enable this feature.
{:else}
Ask your workspace admin to configure AI in workspace settings to enable this
feature.
{/if}
</div>
</div>
{:else}