Hver trait-komponent (Chat, Kanban, Kalender, Editor, Studio) har nå en BlockReceiver med canReceive() som sjekker kompatibilitetsmatrisen. Inkompatible drops viser forklaring og forslag til alternativ. Endringer: - transfer.ts: Per-verktøy compat-sjekker (checkChatCompat, checkKanbanCompat, checkCalendarCompat, checkEditorCompat, checkStudioCompat) + createBlockReceiver factory - types.ts: BlockReceiver utvidet med optional receive() + PlacementIntent type - BlockShell.svelte: Validerer payload på faktisk drop (ikke bare drag-over) - Alle 5 traits: Eksporterer BlockReceiver med canReceive + receive - workspace/+page.svelte: Kobler receivers til BlockShell i spatial canvas - Doc oppdatert til å reflektere faktisk implementasjon Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
92 lines
2.7 KiB
Svelte
92 lines
2.7 KiB
Svelte
<script lang="ts">
|
|
import type { Node } from '$lib/spacetime';
|
|
import { edgeStore, nodeStore } from '$lib/spacetime';
|
|
import { setDragPayload, checkChatCompat, type DragPayload } from '$lib/transfer';
|
|
import type { BlockReceiver, PlacementIntent } from '$lib/components/blockshell/types';
|
|
import TraitPanel from './TraitPanel.svelte';
|
|
|
|
interface Props {
|
|
collection: Node;
|
|
config: Record<string, unknown>;
|
|
userId?: string;
|
|
/** Called when a drop is received on this panel */
|
|
onReceiveDrop?: (payload: DragPayload, intent: PlacementIntent) => void;
|
|
}
|
|
|
|
let { collection, config, userId, onReceiveDrop }: Props = $props();
|
|
|
|
/**
|
|
* BlockReceiver implementation for Chat.
|
|
* Accepts any node — wraps as message with mentions.
|
|
* Ref: docs/retninger/arbeidsflaten.md § Kompatibilitetsmatrise
|
|
*/
|
|
export const receiver: BlockReceiver = {
|
|
canReceive(payload: DragPayload) {
|
|
return checkChatCompat(payload);
|
|
},
|
|
receive(payload: DragPayload) {
|
|
const intent: PlacementIntent = {
|
|
mode: 'lettvekts-triage',
|
|
contextId: collection?.id ?? '',
|
|
contextType: 'chat',
|
|
};
|
|
onReceiveDrop?.(payload, intent);
|
|
return intent;
|
|
}
|
|
};
|
|
|
|
/** Communication nodes linked to this collection */
|
|
const chatNodes = $derived.by(() => {
|
|
const nodes: Node[] = [];
|
|
for (const edge of edgeStore.byTarget(collection.id)) {
|
|
if (edge.edgeType !== 'belongs_to') continue;
|
|
const node = nodeStore.get(edge.sourceId);
|
|
if (node && node.nodeKind === 'communication') {
|
|
nodes.push(node);
|
|
}
|
|
}
|
|
// Also check source edges (collection --has_channel--> communication)
|
|
for (const edge of edgeStore.bySource(collection.id)) {
|
|
if (edge.edgeType !== 'has_channel') continue;
|
|
const node = nodeStore.get(edge.targetId);
|
|
if (node && node.nodeKind === 'communication') {
|
|
nodes.push(node);
|
|
}
|
|
}
|
|
return nodes;
|
|
});
|
|
|
|
function handleDragStart(e: DragEvent, node: Node) {
|
|
if (!e.dataTransfer) return;
|
|
setDragPayload(e.dataTransfer, {
|
|
nodeId: node.id,
|
|
nodeKind: node.nodeKind,
|
|
sourcePanel: 'chat'
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<TraitPanel name="chat" label="Samtaler" icon="💬">
|
|
{#snippet children()}
|
|
{#if chatNodes.length === 0}
|
|
<p class="text-sm text-gray-400">Ingen samtaler knyttet til denne samlingen.</p>
|
|
{:else}
|
|
<ul class="space-y-2">
|
|
{#each chatNodes as node (node.id)}
|
|
<li
|
|
draggable="true"
|
|
ondragstart={(e) => handleDragStart(e, node)}
|
|
class="cursor-grab active:cursor-grabbing"
|
|
>
|
|
<a
|
|
href="/chat/{node.id}"
|
|
class="block rounded border border-gray-100 px-3 py-2 transition-colors hover:border-blue-300 hover:bg-blue-50"
|
|
>
|
|
<span class="text-sm font-medium text-gray-900">{node.title || 'Samtale'}</span>
|
|
</a>
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
{/if}
|
|
{/snippet}
|
|
</TraitPanel>
|