From 0c368f4b24ff3b97d4c3eec6bc5c82f9aa454633 Mon Sep 17 00:00:00 2001 From: dtoro Date: Tue, 10 Mar 2026 22:44:58 +0100 Subject: [PATCH] improve rendering raw --- .../src/components/nodes/RenderingNode.tsx | 83 ++++++++++++++----- frontend/src/hooks/useResizeHeight.ts | 9 +- 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/nodes/RenderingNode.tsx b/frontend/src/components/nodes/RenderingNode.tsx index 50f9a99..3b25b0a 100644 --- a/frontend/src/components/nodes/RenderingNode.tsx +++ b/frontend/src/components/nodes/RenderingNode.tsx @@ -1,11 +1,16 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import nunjucks from 'nunjucks' +import CodeMirror from '@uiw/react-codemirror' +import { javascript } from '@codemirror/lang-javascript' +import { markdown } from '@codemirror/lang-markdown' import { AbstractNodeProps, createAbstractNodeComponent, useAbstractNode, } from '../../lib/abstractNode' import { getConfigContent, getConfigType, getConfigTypeId } from '../../lib/configTypes' +import { useResizeHeight } from '../../hooks/useResizeHeight' +import { plantumlLanguage } from '../../lib/plantumlLanguage' import { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia } from '../ui/empty' import { BaseNode, @@ -20,12 +25,13 @@ import { NodeHeaderTitle } from '../base/NodeHeaderTitle' import { NodeMenubar } from '../base/NodeMenubar' import { NodeStatusIndicator } from '../base/NodeStatusIndicator' import { MenubarItem, MenubarSeparator, MenubarSub, MenubarSubContent, MenubarSubTrigger } from '../ui/menubar' -import { Sparkles, ZoomIn, ZoomOut, RotateCcw, RotateCw, Eye, FileCode } from 'lucide-react' +import { Sparkles, ZoomIn, ZoomOut, RotateCcw, RotateCw, Copy } from 'lucide-react' import { InputHandle } from '../base/NodeHandles' import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch' import { Input } from '../ui/input' import { Button } from '../ui/button' import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group' +import { useTheme } from '../../lib/themeContext' export type RenderingNodeData = { viewportWidth?: number @@ -552,17 +558,17 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) { if (!ctx) return ctx.drawImage(img, 0, 0) canvas.toBlob((blob) => { - if (blob) navigator.clipboard?.write([new ClipboardItem({ 'image/png': blob })]).catch(() => {}) + if (blob) navigator.clipboard?.write([new ClipboardItem({ 'image/png': blob })]).catch(() => { }) }, 'image/png') } - img.onerror = () => {} + img.onerror = () => { } img.src = dataUrl }, [renderedContent, isSvgOutput]) const copySvg = useCallback(() => { if (!renderedContent || !isSvgOutput) return const blob = new Blob([renderedContent], { type: 'image/svg+xml' }) - navigator.clipboard?.write([new ClipboardItem({ 'image/svg+xml': blob })]).catch(() => {}) + navigator.clipboard?.write([new ClipboardItem({ 'image/svg+xml': blob })]).catch(() => { }) }, [renderedContent, isSvgOutput]) const status = loading ? 'loading' : error ? 'error' : renderedContent ? 'success' : 'initial' @@ -579,32 +585,48 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) { updateData({ viewportWidth: viewportDraft.width, viewportHeight: viewportDraft.height }) }, [updateData, viewportDraft.width, viewportDraft.height]) + const { theme } = useTheme() + const [rawEditorHeight, rawEditorContainerRef] = useResizeHeight(180, [viewMode]) + const rawExtensions = useMemo(() => { + const lang = + configTypeId === 'wireframe' + ? javascript() + : configType.language === 'plantuml' + ? plantumlLanguage.extension + : markdown() + return [lang] + }, [configTypeId, configType.language]) + return ( }> - } title={} /> - - -
- {incomingIds.length > 0 && ( + } + title={} + right={ + incomingIds.length > 0 ? ( v && (v === 'preview' || v === 'raw') && setViewMode(v)} - className="shrink-0" + aria-label="View mode" variant="outline" size="sm" + className="gap-0 rounded-md p-0.5 [&>button]:rounded-none [&>button:first-child]:rounded-l-md [&>button:last-child]:rounded-r-md [&>button:not(:first-child)]:border-l-0" > - - + Preview - - + Raw - )} + ) : undefined + } + /> + + +
Rendering…
) : viewMode === 'raw' ? ( -
-
-                                    {resolvedContent ?? '(no resolved config yet)'}
-                                
+
+ +
) : renderedContent ? ( outputType === 'image' ? ( @@ -748,7 +791,7 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) { initialPositionY={0} minScale={0.2} maxScale={4} - centerOnInit={false} + centerOnInit={true} onInit={(ctx) => { if (!ctx?.instance?.wrapperComponent || !ctx?.instance?.contentComponent) return const fitToView = () => { diff --git a/frontend/src/hooks/useResizeHeight.ts b/frontend/src/hooks/useResizeHeight.ts index 0b4b2ea..4dc6242 100644 --- a/frontend/src/hooks/useResizeHeight.ts +++ b/frontend/src/hooks/useResizeHeight.ts @@ -3,8 +3,13 @@ import { useEffect, useRef, useState } from 'react' /** * Returns the height of the observed element, updating when it resizes (e.g. node resize). * Used to give CodeMirror and other components an explicit height that tracks their container. + * @param defaultHeight - Height used until the element is measured. + * @param deps - Optional dependency array; when the ref is attached to a conditionally mounted element, pass deps (e.g. [viewMode]) so the effect re-runs when the element appears. */ -export function useResizeHeight(defaultHeight: number): [number, React.RefObject] { +export function useResizeHeight( + defaultHeight: number, + deps?: React.DependencyList +): [number, React.RefObject] { const ref = useRef(null) const [height, setHeight] = useState(defaultHeight) @@ -21,7 +26,7 @@ export function useResizeHeight(defaultHeight: number): [number, React.RefObject ro.observe(el) setHeight(el.getBoundingClientRect().height) return () => ro.disconnect() - }, []) + }, deps ?? []) return [height, ref] }