Files
Containr/app/frontend/src/features/workspace/model.ts
T
Tomas DvorakandDevin e57f0470eb feat(canvas): fullscreen Railway-style workspace canvas
Rebuild the project canvas as a full-viewport graph instead of a boxed
66vh panel. Slim workspace top bar now holds the view tabs, project meta
and actions; the canvas fills all remaining space.

- Floating glass controls: env filter + group pill (top-left), zoom out /
  live zoom % / zoom in / fit / tidy pill (bottom-left), dark MiniMap
  (bottom-right), hint chip, demo badge.
- Inspector panel: selecting a node opens a right-side glass panel with
  status, image, address, inferred connections incl. variable reasons,
  and deploy/restart/stop/delete actions. Esc or pane click closes.
- Auto-layout: dependency-depth layered layout (consumers left,
  datastores right) for first-visit placement and the Tidy action.
  Unpositioned services park right of existing bounds, not at (0,0).
- Nodes: fixed 240px width (was 210 vs 220-280 CSS mismatch), per-type
  icons/colors incl. cron, spinner icon while deploying, hover lift.
- Edges: lime bezier strokes, animated dashes while either endpoint is
  deploying, hover glow updated from stale purple to accent.
- Groups: off-brand crimson replaced with accent tokens, double-click
  title to rename inline.
- Keyboard: F fit, 0 reset zoom, +/- zoom, Esc deselect. Marquee select
  on Shift-drag. Double-click zoom disabled, dead connection drags off.
- Mobile: icon-only tabs, minimap/hint hidden under md.
- Retake docs/screenshots/canvas.png + site copy (1600x900 demo).

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-23 17:00:50 +02:00

71 lines
1.2 KiB
TypeScript

import type { ServiceEntity } from '@/lib/api-client';
export type CanvasPoint = {
x: number;
y: number;
};
export type CanvasGroup = {
id: string;
title: string;
position: CanvasPoint;
width: number;
height: number;
};
export type CanvasNodeLayout = {
serviceId: string;
position: CanvasPoint;
groupId?: string;
};
export type CanvasEdge = {
id: string;
sourceServiceId: string;
targetServiceId: string;
};
export type CanvasViewport = {
x: number;
y: number;
zoom: number;
};
export type ProjectCanvasMetadata = {
groups: CanvasGroup[];
nodes: CanvasNodeLayout[];
edges: CanvasEdge[];
viewport: CanvasViewport;
};
export const DEFAULT_VIEWPORT: CanvasViewport = {
x: 0,
y: 0,
zoom: 1,
};
export function createDefaultCanvasMetadata(
services: ServiceEntity[],
layout?: Map<string, CanvasPoint>,
): ProjectCanvasMetadata {
const nodes = services.map((service, index) => {
const col = index % 3;
const row = Math.floor(index / 3);
return {
serviceId: service.id,
position: layout?.get(service.id) ?? {
x: 70 + col * 260,
y: 80 + row * 170,
},
};
});
return {
groups: [],
nodes,
edges: [],
viewport: DEFAULT_VIEWPORT,
};
}