Unverified 提交 e9a079fb authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Device toggle (#2327)

#skip-bb fixes https://github.com/dyad-sh/dyad/issues/2318 <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Persist the selected device mode (desktop, tablet, mobile) in user settings so it stays after app rebuilds. Adds an end-to-end test to verify the mode persists through a rebuild. - **New Features** - PreviewIframe reads and writes previewDeviceMode via useSettings, replacing local state. - Added DeviceModeSchema and previewDeviceMode to UserSettingsSchema. - **Bug Fixes** - Stabilized e2e: added a persistence test, use po.clickRebuild(), and wait for preview loading to appear/disappear with a final assertion timeout. <sup>Written for commit c801c37c314413ba23c0174dc7bb401193826389. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com>
上级 3b13f338
...@@ -76,4 +76,36 @@ test.describe("Toggle Screen Size Tests", () => { ...@@ -76,4 +76,36 @@ test.describe("Toggle Screen Size Tests", () => {
); );
expect(mobileWidth).toBe("375"); expect(mobileWidth).toBe("375");
}); });
testSkipIfWindows(
"should persist device mode after rebuild",
async ({ po }) => {
test.setTimeout(Timeout.EXTRA_LONG * 2);
await setupApp(po);
const deviceModeButton = po.page.locator(
'[data-testid="device-mode-button"]',
);
const previewIframe = po.page.locator(
'[data-testid="preview-iframe-element"]',
);
// Switch to mobile mode
await deviceModeButton.click();
await po.page.locator('[aria-label="Mobile view"]').click();
await expect(previewIframe).toHaveAttribute("style", /width:\s*375px/);
// Trigger rebuild
await po.clickRebuild();
await expect(po.locateLoadingAppPreview()).toBeVisible();
await expect(po.locateLoadingAppPreview()).not.toBeVisible({
timeout: Timeout.EXTRA_LONG,
});
// Verify mobile mode persists after rebuild
await expect(previewIframe).toHaveAttribute("style", /width:\s*375px/, {
timeout: Timeout.LONG,
});
},
);
}); });
...@@ -60,10 +60,12 @@ import { ...@@ -60,10 +60,12 @@ import {
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { useRunApp } from "@/hooks/useRunApp"; import { useRunApp } from "@/hooks/useRunApp";
import { useSettings } from "@/hooks/useSettings";
import { useShortcut } from "@/hooks/useShortcut"; import { useShortcut } from "@/hooks/useShortcut";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { normalizePath } from "../../../shared/normalizePath"; import { normalizePath } from "../../../shared/normalizePath";
import { showError } from "@/lib/toast"; import { showError } from "@/lib/toast";
import type { DeviceMode } from "@/lib/schemas";
import { AnnotatorOnlyForPro } from "./AnnotatorOnlyForPro"; import { AnnotatorOnlyForPro } from "./AnnotatorOnlyForPro";
import { useAttachments } from "@/hooks/useAttachments"; import { useAttachments } from "@/hooks/useAttachments";
import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo"; import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo";
...@@ -178,6 +180,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -178,6 +180,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
const { streamMessage } = useStreamChat(); const { streamMessage } = useStreamChat();
const { routes: availableRoutes } = useParseRouter(selectedAppId); const { routes: availableRoutes } = useParseRouter(selectedAppId);
const { restartApp } = useRunApp(); const { restartApp } = useRunApp();
const { settings, updateSettings } = useSettings();
const { userBudget } = useUserBudgetInfo(); const { userBudget } = useUserBudgetInfo();
const isProMode = !!userBudget; const isProMode = !!userBudget;
...@@ -212,8 +215,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -212,8 +215,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
const [hasStaticText, setHasStaticText] = useState(false); const [hasStaticText, setHasStaticText] = useState(false);
// Device mode state // Device mode state
type DeviceMode = "desktop" | "tablet" | "mobile"; const deviceMode: DeviceMode = settings?.previewDeviceMode ?? "desktop";
const [deviceMode, setDeviceMode] = useState<DeviceMode>("desktop");
const [isDevicePopoverOpen, setIsDevicePopoverOpen] = useState(false); const [isDevicePopoverOpen, setIsDevicePopoverOpen] = useState(false);
// Device configurations // Device configurations
...@@ -963,7 +965,8 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -963,7 +965,8 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
data-testid="device-mode-button" data-testid="device-mode-button"
onClick={() => { onClick={() => {
// Toggle popover open/close // Toggle popover open/close
if (isDevicePopoverOpen) setDeviceMode("desktop"); if (isDevicePopoverOpen)
updateSettings({ previewDeviceMode: "desktop" });
setIsDevicePopoverOpen(!isDevicePopoverOpen); setIsDevicePopoverOpen(!isDevicePopoverOpen);
}} }}
className={cn( className={cn(
...@@ -986,7 +989,9 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -986,7 +989,9 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
value={deviceMode} value={deviceMode}
onValueChange={(value) => { onValueChange={(value) => {
if (value) { if (value) {
setDeviceMode(value as DeviceMode); updateSettings({
previewDeviceMode: value as DeviceMode,
});
setIsDevicePopoverOpen(false); setIsDevicePopoverOpen(false);
} }
}} }}
......
...@@ -235,6 +235,9 @@ export type ReleaseChannel = z.infer<typeof ReleaseChannelSchema>; ...@@ -235,6 +235,9 @@ export type ReleaseChannel = z.infer<typeof ReleaseChannelSchema>;
export const ZoomLevelSchema = z.enum(["90", "100", "110", "125", "150"]); export const ZoomLevelSchema = z.enum(["90", "100", "110", "125", "150"]);
export type ZoomLevel = z.infer<typeof ZoomLevelSchema>; export type ZoomLevel = z.infer<typeof ZoomLevelSchema>;
export const DeviceModeSchema = z.enum(["desktop", "tablet", "mobile"]);
export type DeviceMode = z.infer<typeof DeviceModeSchema>;
export const SmartContextModeSchema = z.enum([ export const SmartContextModeSchema = z.enum([
"balanced", "balanced",
"conservative", "conservative",
...@@ -294,6 +297,7 @@ export const UserSettingsSchema = z ...@@ -294,6 +297,7 @@ export const UserSettingsSchema = z
defaultChatMode: ChatModeSchema.optional(), defaultChatMode: ChatModeSchema.optional(),
acceptedCommunityCode: z.boolean().optional(), acceptedCommunityCode: z.boolean().optional(),
zoomLevel: ZoomLevelSchema.optional(), zoomLevel: ZoomLevelSchema.optional(),
previewDeviceMode: DeviceModeSchema.optional(),
enableAutoFixProblems: z.boolean().optional(), enableAutoFixProblems: z.boolean().optional(),
enableNativeGit: z.boolean().optional(), enableNativeGit: z.boolean().optional(),
......
...@@ -136,7 +136,6 @@ ...@@ -136,7 +136,6 @@
"version": "20.17.46", "version": "20.17.46",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.19.2" "undici-types": "~6.19.2"
} }
...@@ -1528,7 +1527,6 @@ ...@@ -1528,7 +1527,6 @@
"version": "5.8.3", "version": "5.8.3",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论