Files
windmill/frontend/src/lib/components/flows/CreateActionsApp.svelte
T
Ruben FiszelandClaude Opus 5.5 6064aecdec feat: stop cross-origin isolating the raw app editor (#11411)
* feat: stop cross-origin isolating the raw app editor

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166irwM15U8vZjc58MsU54U

* docs: drop stale raw app editor reload comments

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166irwM15U8vZjc58MsU54U

* feat: drop wm_coep from the default raw app embed snippet

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166irwM15U8vZjc58MsU54U

* chore: pin the UI builder that type-checks without cross-origin isolation

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166irwM15U8vZjc58MsU54U

* docs: name the proxy-isolated case behind the remaining isolation checks

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166irwM15U8vZjc58MsU54U

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 16:52:49 +02:00

164 lines
5.0 KiB
Svelte

<script lang="ts">
import { goto } from '$lib/navigation'
import { base } from '$lib/base'
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 Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Tab from '$lib/components/common/tabs/Tab.svelte'
import Modal from '$lib/components/common/modal/Modal.svelte'
import { LayoutDashboard, Loader2, Plus, Code2 } from 'lucide-svelte'
import { importStore } from '../apps/store'
import YAML from 'yaml'
let drawer: Drawer | undefined = undefined
let pendingRaw: string = $state('')
let importType: 'yaml' | 'json' = $state('yaml')
let appKind: 'lowcode' | 'fullcode' = $state('lowcode')
let appTypeModalOpen = $state(false)
async function importRaw() {
const parsed = importType === 'yaml' ? YAML.parse(pendingRaw) : JSON.parse(pendingRaw)
if (appKind === 'fullcode') {
// The raw app editor reads the payload from sessionStorage on mount.
sessionStorage.setItem('rawAppImport', JSON.stringify(parsed))
await goto('/apps_raw/add')
} else {
$importStore = parsed
await goto('/apps/add')
}
drawer?.closeDrawer?.()
}
function openAppTypeModal() {
appTypeModalOpen = true
}
function selectLowCode() {
appTypeModalOpen = false
goto(`${base}/apps/add`)
}
function selectFullCode() {
appTypeModalOpen = false
goto(`${base}/apps_raw/add`)
}
</script>
<!-- Buttons -->
<div class="flex flex-row gap-2">
<Button
id="create-app-button"
aiId="apps-create-actions-app"
aiDescription="Create a new app"
unifiedSize="lg"
startIcon={{ icon: Plus }}
endIcon={{ icon: LayoutDashboard }}
on:click={openAppTypeModal}
variant="accent"
dropdownItems={[
{
label: 'Import low-code app',
onClick: () => {
appKind = 'lowcode'
importType = 'yaml'
drawer?.toggleDrawer?.()
}
},
{
label: 'Import full-code app',
onClick: () => {
appKind = 'fullcode'
importType = 'yaml'
drawer?.toggleDrawer?.()
}
}
]}
>
<div class="flex flex-row items-center"> App </div>
</Button>
</div>
<!-- App Type Selection Modal -->
<Modal bind:open={appTypeModalOpen} title="Choose your app builder" class="sm:max-w-3xl">
<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.
<br /><br />
Better for complex apps or apps that require full flexibility and control.
</p>
</div>
</button>
</div>
</div>
</Modal>
<!-- Import Drawer -->
<Drawer bind:this={drawer} size="800px">
<DrawerContent
title={appKind === 'fullcode' ? 'Import full-code app' : 'Import low-code app'}
on:close={() => drawer?.toggleDrawer?.()}
>
<Tabs bind:selected={importType}>
<Tab value="yaml" label="YAML" />
<Tab value="json" label="JSON" />
{#snippet content()}
<div class="relative pt-2 h-full">
{#key importType}
{#await import('$lib/components/SimpleEditor.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
bind:code={pendingRaw}
lang={importType}
class="h-full"
fixedOverflowWidgets={false}
/>
{/await}
{/key}
</div>
{/snippet}
</Tabs>
{#snippet actions()}
<Button size="sm" on:click={importRaw}>Import</Button>
{/snippet}
</DrawerContent>
</Drawer>