Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 177 additions & 74 deletions packages/vite-plugin/src/dev-host/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
MIN_DETAILS_WIDTH,
MIN_DEVTOOLS_HEIGHT,
MIN_IFRAME_HEIGHT,
MIN_NARROW_IFRAME_HEIGHT,
SPLITTER_SIZE,
} from './constants.js';
import type {
Expand Down Expand Up @@ -41,6 +42,7 @@ import {
import { MessageLogPane } from './components/MessageLogPane.js';
import { PanelTabs } from './components/PanelTabs.js';
import { ResizeHandle } from './components/ResizeHandle.js';
import { ToggleGroup } from './components/ui/ToggleGroup.js';
import { RozeniteLogo } from './components/icons.js';

type CSSVariables = CSSProperties & Record<`--${string}`, string>;
Expand All @@ -50,6 +52,8 @@ type AppProps = DevHostState & {
presets: DevHostPresetEntry[];
};

type MobileDevtoolsTab = 'log' | 'actions';

const getViewportMatch = () => {
return window.matchMedia('(max-width: 960px)').matches;
};
Expand All @@ -68,6 +72,7 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:
const [detailsWidth, setDetailsWidth] = useState(DETAILS_PANEL_WIDTH);
const [activeResizeHandle, setActiveResizeHandle] = useState<ResizeHandleId | null>(null);
const [isNarrowViewport, setIsNarrowViewport] = useState(getViewportMatch);
const [activeMobileTab, setActiveMobileTab] = useState<MobileDevtoolsTab>('log');
const [iframeLoadNonce, setIframeLoadNonce] = useState(0);
const workspaceRef = useRef<HTMLElement | null>(null);
const logWorkspaceRef = useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -115,6 +120,8 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:
})();
const canDispatch = hasCommandType && hasValidCommandPayload;
const panelDescription = packageDescription.trim();
const isDetailsVisible = isDetailsOpen && selectedMessage !== null;
const iframeMinHeight = isNarrowViewport ? MIN_NARROW_IFRAME_HEIGHT : MIN_IFRAME_HEIGHT;

useEffect(() => {
document.title = `${packageName} Dev Host`;
Expand Down Expand Up @@ -153,6 +160,26 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:
};
}, []);

useEffect(() => {
const workspace = workspaceRef.current;
if (!workspace) {
return;
}

const bounds = workspace.getBoundingClientRect();
const maxHeight = Math.max(MIN_DEVTOOLS_HEIGHT, bounds.height - iframeMinHeight - 12);

setDevToolsHeight((current) => clamp(current, MIN_DEVTOOLS_HEIGHT, maxHeight));
}, [iframeMinHeight]);

useEffect(() => {
if (!isNarrowViewport) {
return;
}

setActiveMobileTab('log');
}, [isNarrowViewport]);

const selectPanel = (value: string) => {
const nextPanel = panels.find((panel) => panel.source === value);
if (!nextPanel) {
Expand All @@ -170,8 +197,6 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:

registerMessage(nextEntry);
setMessages((current) => [nextEntry, ...current]);
setSelectedMessageId(nextEntry.id);
setIsDetailsOpen(true);
};

useEffect(() => {
Expand Down Expand Up @@ -271,6 +296,7 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:
setMessages([]);
setSelectedMessageId(null);
setIsDetailsOpen(false);
setActiveMobileTab('log');
};

const resizeDevtoolsHeight = (event: PointerEvent) => {
Expand All @@ -281,7 +307,7 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:

const bounds = workspace.getBoundingClientRect();
const nextHeight = bounds.bottom - event.clientY;
const maxHeight = Math.max(MIN_DEVTOOLS_HEIGHT, bounds.height - MIN_IFRAME_HEIGHT - 12);
const maxHeight = Math.max(MIN_DEVTOOLS_HEIGHT, bounds.height - iframeMinHeight - 12);

setDevToolsHeight(clamp(nextHeight, MIN_DEVTOOLS_HEIGHT, maxHeight));
};
Expand All @@ -293,29 +319,47 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:
}

const bounds = devtools.getBoundingClientRect();
const nextWidth = isNarrowViewport
? bounds.bottom - event.clientY
: bounds.right - event.clientX;
const nextWidth = bounds.right - event.clientX;
const maxWidth = Math.max(MIN_COMMAND_WIDTH, bounds.width - 280);

setCommandWidth(clamp(nextWidth, MIN_COMMAND_WIDTH, maxWidth));
};

const resizeDetailsPane = (event: PointerEvent) => {
const logWorkspace = logWorkspaceRef.current;
if (!logWorkspace || !isDetailsOpen || !selectedMessage) {
if (!logWorkspace || !isDetailsVisible || isNarrowViewport) {
return;
}

const bounds = logWorkspace.getBoundingClientRect();
const nextWidth = isNarrowViewport
? bounds.bottom - event.clientY
: bounds.right - event.clientX;
const nextWidth = bounds.right - event.clientX;
const maxWidth = Math.max(MIN_DETAILS_WIDTH, bounds.width - 280 - SPLITTER_SIZE);

setDetailsWidth(clamp(nextWidth, MIN_DETAILS_WIDTH, maxWidth));
};

const handleMessageSelect = (messageId: string) => {
setSelectedMessageId(messageId);
setIsDetailsOpen(true);

if (isNarrowViewport) {
setActiveMobileTab('log');
}
};

const handleDetailsClose = () => {
setIsDetailsOpen(false);

if (isNarrowViewport) {
setActiveMobileTab('log');
}
};

const handleMobileTabChange = (value: string) => {
const nextTab = value as MobileDevtoolsTab;
setActiveMobileTab(nextTab);
};

const handleDispatch = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();

Expand Down Expand Up @@ -386,78 +430,137 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:
onPointerDown={(event) => startResize('devtools-height', event, resizeDevtoolsHeight)}
/>

<section
ref={devtoolsRef}
className="rz-devtools"
style={
{
'--rz-command-width': `${commandWidth}px`,
'--rz-command-splitter-width': `${SPLITTER_SIZE}px`,
} as CSSVariables
}
>
<div
ref={logWorkspaceRef}
className="rz-log-workspace"
{isNarrowViewport ? (
<section ref={devtoolsRef} className="rz-devtools-mobile">
<div className="rz-devtools-mobile-tabs">
<div className="rz-devtools-mobile-toggle">
<ToggleGroup
aria-label="DevTools sections"
value={activeMobileTab}
onChange={handleMobileTabChange}
options={[
{ key: 'log', label: 'Log' },
{ key: 'actions', label: 'Actions' },
]}
/>
</div>

<div className="rz-devtools-mobile-panel">
{activeMobileTab === 'log' ? (
isDetailsVisible ? (
<MessageDetailsPane
selectedMessage={selectedMessage}
isOpen={true}
isNarrowViewport={true}
activeResizeHandle={activeResizeHandle}
onClose={handleDetailsClose}
onUseMessage={(message) => {
const nextValues = getDispatcherValuesFromMessage(message);
setCommandType(nextValues.commandType);
setCommandPayload(nextValues.commandPayload);
setIsDetailsOpen(false);
setActiveMobileTab('actions');
}}
onResizeStart={(event) => startResize('details-width', event, resizeDetailsPane)}
/>
) : (
<MessageLogPane
messages={messages}
selectedMessageId={selectedMessageId}
onSelectMessage={handleMessageSelect}
onClearMessages={clearMessages}
/>
)
) : (
<DispatchForm
commandType={commandType}
commandPayload={commandPayload}
flows={flows}
flowRuns={flowRuns}
hasRunningFlow={hasRunningFlow}
presets={presets}
canDispatch={canDispatch}
onRunFlow={runFlow}
onStopFlow={stopFlow}
onCommandTypeChange={setCommandType}
onCommandPayloadChange={setCommandPayload}
onApplyPreset={applyPreset}
onReset={resetForm}
onSubmit={handleDispatch}
/>
)}
</div>
</div>
</section>
) : (
<section
ref={devtoolsRef}
className="rz-devtools"
style={
{
'--rz-details-width':
isDetailsOpen && selectedMessage ? `${detailsWidth}px` : '0px',
'--rz-details-splitter-width':
isDetailsOpen && selectedMessage ? `${SPLITTER_SIZE}px` : '0px',
'--rz-command-width': `${commandWidth}px`,
'--rz-command-splitter-width': `${SPLITTER_SIZE}px`,
} as CSSVariables
}
>
<MessageLogPane
messages={messages}
selectedMessageId={selectedMessageId}
onSelectMessage={(messageId) => {
setSelectedMessageId(messageId);
setIsDetailsOpen(true);
}}
onClearMessages={clearMessages}
<div
ref={logWorkspaceRef}
className="rz-log-workspace"
style={
{
'--rz-details-width': isDetailsVisible ? `${detailsWidth}px` : '0px',
'--rz-details-splitter-width': isDetailsVisible ? `${SPLITTER_SIZE}px` : '0px',
} as CSSVariables
}
>
<MessageLogPane
messages={messages}
selectedMessageId={selectedMessageId}
onSelectMessage={handleMessageSelect}
onClearMessages={clearMessages}
/>

<MessageDetailsPane
selectedMessage={selectedMessage}
isOpen={isDetailsOpen}
isNarrowViewport={false}
activeResizeHandle={activeResizeHandle}
onClose={handleDetailsClose}
onUseMessage={(message) => {
const nextValues = getDispatcherValuesFromMessage(message);
setCommandType(nextValues.commandType);
setCommandPayload(nextValues.commandPayload);
}}
onResizeStart={(event) => startResize('details-width', event, resizeDetailsPane)}
/>
</div>

<ResizeHandle
className="rz-column-resize-handle"
isDragging={activeResizeHandle === 'command-width'}
orientation="vertical"
label="Resize command dispatcher"
onPointerDown={(event) => startResize('command-width', event, resizeCommandPane)}
/>

<MessageDetailsPane
selectedMessage={selectedMessage}
isOpen={isDetailsOpen}
isNarrowViewport={isNarrowViewport}
activeResizeHandle={activeResizeHandle}
onClose={() => setIsDetailsOpen(false)}
onUseMessage={(message) => {
const nextValues = getDispatcherValuesFromMessage(message);
setCommandType(nextValues.commandType);
setCommandPayload(nextValues.commandPayload);
}}
onResizeStart={(event) => startResize('details-width', event, resizeDetailsPane)}
<DispatchForm
commandType={commandType}
commandPayload={commandPayload}
flows={flows}
flowRuns={flowRuns}
hasRunningFlow={hasRunningFlow}
presets={presets}
canDispatch={canDispatch}
onRunFlow={runFlow}
onStopFlow={stopFlow}
onCommandTypeChange={setCommandType}
onCommandPayloadChange={setCommandPayload}
onApplyPreset={applyPreset}
onReset={resetForm}
onSubmit={handleDispatch}
/>
</div>

<ResizeHandle
className="rz-column-resize-handle"
isDragging={activeResizeHandle === 'command-width'}
orientation={isNarrowViewport ? 'horizontal' : 'vertical'}
label="Resize command dispatcher"
onPointerDown={(event) => startResize('command-width', event, resizeCommandPane)}
/>

<DispatchForm
commandType={commandType}
commandPayload={commandPayload}
flows={flows}
flowRuns={flowRuns}
hasRunningFlow={hasRunningFlow}
presets={presets}
canDispatch={canDispatch}
onRunFlow={runFlow}
onStopFlow={stopFlow}
onCommandTypeChange={setCommandType}
onCommandPayloadChange={setCommandPayload}
onApplyPreset={applyPreset}
onReset={resetForm}
onSubmit={handleDispatch}
/>
</section>
</section>
)}
</main>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ export const MessageDetailsPane = ({

return (
<>
<ResizeHandle
className="rz-column-resize-handle"
isDragging={activeResizeHandle === 'details-width'}
isHidden={isHidden}
orientation={isNarrowViewport ? 'horizontal' : 'vertical'}
label="Resize message details"
onPointerDown={onResizeStart}
/>
{!isNarrowViewport ? (
<ResizeHandle
className="rz-column-resize-handle"
isDragging={activeResizeHandle === 'details-width'}
isHidden={isHidden}
orientation="vertical"
label="Resize message details"
onPointerDown={onResizeStart}
/>
) : null}

<div className="rz-pane" data-hidden={isHidden} aria-hidden={isHidden}>
<div className="rz-sidebar">
Expand Down
1 change: 1 addition & 0 deletions packages/vite-plugin/src/dev-host/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const DEV_HOST_CONFIG_GLOBAL_KEY = '__rozenite-dev-config__';
export const DEFAULT_DEVTOOLS_HEIGHT = 288;
export const MIN_DEVTOOLS_HEIGHT = 180;
export const MIN_IFRAME_HEIGHT = 220;
export const MIN_NARROW_IFRAME_HEIGHT = 140;
export const DETAILS_PANEL_WIDTH = 360;
export const MIN_DETAILS_WIDTH = 280;
export const DEFAULT_COMMAND_WIDTH = 320;
Expand Down
Loading
Loading