Files
toasty/src/lib/components/schema/DragDropCanvas.svelte
2025-09-15 03:03:36 +00:00

146 lines
4.2 KiB
Svelte

<script lang="ts">
import { schemaStore, addSchema, addEndpoint, selectItem, type SchemaItem, type APIEndpoint } from '$lib/stores/schema';
import SchemaNode from './SchemaNode.svelte';
import EndpointNode from './EndpointNode.svelte';
import { Button } from '$lib/components/ui/button';
import { Plus } from 'lucide-svelte';
let canvasElement: HTMLDivElement;
let isDragOver = $state(false);
function handleDragOver(event: DragEvent) {
event.preventDefault();
isDragOver = true;
}
function handleDragLeave(event: DragEvent) {
event.preventDefault();
isDragOver = false;
}
function handleDrop(event: DragEvent) {
event.preventDefault();
isDragOver = false;
const rect = canvasElement.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// Get drag data
const schemaType = event.dataTransfer?.getData('schema-type');
const schemaFormat = event.dataTransfer?.getData('schema-format');
const apiMethod = event.dataTransfer?.getData('api-method');
const apiComponent = event.dataTransfer?.getData('api-component');
if (schemaType) {
// Create new schema item
const newSchema: Omit<SchemaItem, 'id'> = {
type: schemaType as any,
name: `new_${schemaType}`,
required: false,
description: `A ${schemaType} field`,
format: schemaFormat || undefined,
children: schemaType === 'object' || schemaType === 'array' ? [] : undefined,
x,
y
};
addSchema(newSchema);
} else if (apiMethod) {
// Create new endpoint
const newEndpoint: Omit<APIEndpoint, 'id'> = {
method: apiMethod as any,
path: `/${apiMethod.toLowerCase()}/resource`,
summary: `${apiMethod} operation`,
description: `${apiMethod} endpoint description`,
x,
y
};
addEndpoint(newEndpoint);
}
}
function handleItemClick(item: SchemaItem | APIEndpoint) {
selectItem(item);
}
function handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Enter') {
// Trigger the button click event on Enter key press
const button = event.target as HTMLButtonElement;
button.click();
}
}
</script>
<!-- Created interactive drag and drop canvas -->
<div
bind:this={canvasElement}
class="relative min-h-96 border-2 border-dashed rounded-lg transition-colors {isDragOver ? 'border-primary bg-primary/5' : 'border-border bg-muted/20'}"
role="button"
aria-label="Drop Components Here"
ondragover={handleDragOver}
ondragleave={handleDragLeave}
ondrop={handleDrop}
tabindex="0"
>
{#if $schemaStore.schemas.length === 0 && $schemaStore.endpoints.length === 0}
<div class="absolute inset-0 flex items-center justify-center">
<div class="text-center">
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center">
<Plus class="w-8 h-8 text-primary" />
</div>
<h3 class="text-lg font-semibold text-foreground mb-2">Drop Components Here</h3>
<p class="text-muted-foreground mb-4">Drag schema types and API components from the sidebar to build your OpenAPI specification</p>
<Button onkeydown={handleKeyDown} tabindex="0">
<Plus class="w-4 h-4 mr-2" />
Add Component
</Button>
</div>
</div>
{:else}
<!-- Render dropped schemas -->
{#each $schemaStore.schemas as schema}
<div
class="absolute cursor-pointer"
style="left: {schema.x}px; top: {schema.y}px;"
role="button"
aria-label={`Click to select ${schema.name}`}
onclick={() => handleItemClick(schema)}
onkeydown={(event) => {
if (event.key === 'Enter') {
handleItemClick(schema);
}
}}
tabindex="0"
>
<SchemaNode
{...schema}
selected={$schemaStore.selectedItem?.id === schema.id}
/>
</div>
{/each}
<!-- Render dropped endpoints -->
{#each $schemaStore.endpoints as endpoint}
<div
class="absolute cursor-pointer"
style="left: {endpoint.x}px; top: {endpoint.y}px;"
role="button"
aria-label={`Click to select ${endpoint.path}`}
onclick={() => handleItemClick(endpoint)}
onkeydown={(event) => {
if (event.key === 'Enter') {
handleItemClick(endpoint);
}
}}
tabindex="0"
>
<EndpointNode
{...endpoint}
selected={$schemaStore.selectedItem?.id === endpoint.id}
/>
</div>
{/each}
{/if}
</div>