diff --git a/packages/web/src/components/display-settings-dialog.tsx b/packages/web/src/components/display-settings-dialog.tsx new file mode 100644 index 00000000..ae20609a --- /dev/null +++ b/packages/web/src/components/display-settings-dialog.tsx @@ -0,0 +1,86 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogFooter, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { DialogProps } from "@radix-ui/react-dialog"; +import { useState, FormEvent } from "react"; +import { + DisplaySettings, + sanitizeDisplaySettings, +} from "@/lib/display-settings"; + +interface DisplaySettingsDialogProps extends DialogProps { + settings: DisplaySettings, + onAccept: (settings: DisplaySettings) => void; +} + +export default function DisplaySettingsDialog({ + settings, + onAccept, + ...props +}: DisplaySettingsDialogProps) { + + const [unsavedSettings, setUnsavedSettings] = useState({...settings}); + const sanitizeAndSetUnsavedSettings = (settings: DisplaySettings) => + setUnsavedSettings(sanitizeDisplaySettings(settings)); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + onAccept(unsavedSettings); + props.onOpenChange && props.onOpenChange(false); + }; + + return ( + + +
+ + Change display settings + +
+
+ + sanitizeAndSetUnsavedSettings({ + ...unsavedSettings, + canvasPixelSize: parseInt(e.target.value, 10), + })} + /> +
+
+ + sanitizeAndSetUnsavedSettings({ + ...unsavedSettings, + showCanvas: e.target.checked, + })} + /> +
+
+ + + +
+
+
+ ); +} diff --git a/packages/web/src/components/hydra-canvas.tsx b/packages/web/src/components/hydra-canvas.tsx index 504ccfb5..75c7f03c 100644 --- a/packages/web/src/components/hydra-canvas.tsx +++ b/packages/web/src/components/hydra-canvas.tsx @@ -1,13 +1,15 @@ import React from "react"; import { cn } from "@/lib/utils"; +import { DisplaySettings } from "@/lib/display-settings"; interface HydraCanvasProps { fullscreen?: boolean; + displaySettings: DisplaySettings; } const HydraCanvas = React.forwardRef( ( - { fullscreen }: HydraCanvasProps, + { fullscreen, displaySettings }: HydraCanvasProps, ref: React.ForwardedRef ) => ( ) ); diff --git a/packages/web/src/components/session-command-dialog.tsx b/packages/web/src/components/session-command-dialog.tsx index 49ec3df3..3ba62dc7 100644 --- a/packages/web/src/components/session-command-dialog.tsx +++ b/packages/web/src/components/session-command-dialog.tsx @@ -29,24 +29,30 @@ import { Palette, Settings, Share, + Monitor, } from "lucide-react"; import { Link } from "react-router-dom"; import { useState } from "react"; import { EditorSettings } from "./editor"; +import { DisplaySettings } from "@/lib/display-settings"; interface SessionCommandDialogProps extends CommandDialogProps { editorSettings: EditorSettings; onEditorSettingsChange: (settings: EditorSettings) => void; + displaySettings: DisplaySettings; + onDisplaySettingsChange: (settings: DisplaySettings) => void; onSessionChangeUsername: () => void; onSessionNew: () => void; onSessionShareUrl: () => void; onLayoutAdd: () => void; onLayoutRemove: () => void; onLayoutConfigure: () => void; + onEditorChangeDisplaySettings: () => void; } export default function SessionCommandDialog({ editorSettings, + displaySettings, onEditorSettingsChange, ...props }: SessionCommandDialogProps) { @@ -144,6 +150,7 @@ export default function SessionCommandDialog({ Remove Pane + setPages([...pages, "fonts"])}> @@ -194,6 +201,15 @@ export default function SessionCommandDialog({ + + + + + Change display settings + + + + {/* diff --git a/packages/web/src/components/web-target-iframe.tsx b/packages/web/src/components/web-target-iframe.tsx index 1a9c829b..88b17f8f 100644 --- a/packages/web/src/components/web-target-iframe.tsx +++ b/packages/web/src/components/web-target-iframe.tsx @@ -1,17 +1,20 @@ import { useQuery } from "@/hooks/use-query"; import { EvalMessage, Session } from "@flok-editor/session"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; +import { DisplaySettings } from "@/lib/display-settings"; export interface WebTargetIframeProps { target: string; session: Session | null; + displaySettings: DisplaySettings; } -export const WebTargetIframe = ({ target, session }: WebTargetIframeProps) => { +export const WebTargetIframe = ({ target, session, displaySettings }: WebTargetIframeProps) => { const ref = useRef(null); const query = useQuery(); const noWebEval = query.get("noWebEval")?.split(",") || []; + const [firstEval, setFirstEval] = useState(true); // Check if we should load the target if (noWebEval.includes(target) || noWebEval.includes("*")) { @@ -28,6 +31,7 @@ export const WebTargetIframe = ({ target, session }: WebTargetIframeProps) => { body: msg, }; ref.current?.contentWindow?.postMessage(payload, "*"); + setFirstEval(false); }; session.on(`eval:${target}`, handler); @@ -43,6 +47,7 @@ export const WebTargetIframe = ({ target, session }: WebTargetIframeProps) => { if (!ref.current) return; const interactionMessage = { type: "user-interaction" }; ref.current.contentWindow?.postMessage(interactionMessage, "*"); + setFirstEval(false); }; window.addEventListener("click", handleUserInteraction); @@ -54,6 +59,20 @@ export const WebTargetIframe = ({ target, session }: WebTargetIframeProps) => { }; }, [ref]); + // Post display settings to iframe when the settings change, or when the + // first eval occurs. The latter is a bit of a hack that prevents us from + // having to detect when the iframe is ready to receive messages (adding + // `ref` to the useEffect() dependencies doesn't do the trick), and it may + // break if flok begins eval'ing on load (currently nothing is eval'd on + // load). + useEffect(() => { + const message = { + type: "settings", + body: { displaySettings }, + }; + ref.current?.contentWindow?.postMessage(message, "*"); + }, [displaySettings, firstEval]); + return (