Skip to content
Merged
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
1 change: 1 addition & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const clientSettings: ClientSettings = {
browserRecordingFrameRate: 60,
browserLinkTarget: "app",
browserAutoShowFloatingPreview: false,
deviceView: "flat",
browserProfiles: [{ id: "work", name: "Work", kind: "persistent" }],
browserDefaultProfileId: "work",
confirmQuit: "double-click",
Expand Down
12 changes: 7 additions & 5 deletions apps/web/src/components/device/DeviceStreamView.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { DevicePlatform, EnvironmentId } from "@t3tools/contracts";
import type { DevicePlatform, DeviceView, EnvironmentId } from "@t3tools/contracts";
import { useAtomValue } from "@effect/atom-react";
import { AsyncResult, Atom } from "effect/reactivity";
import { useId, useRef, useState, type ReactNode } from "react";

import { updateClientSettings, useClientSettings } from "~/hooks/useSettings";
import { cn } from "~/lib/utils";
import { Button } from "~/components/ui/button";
import { renewDeviceHubAccess, useDeviceHubAccess } from "~/state/device";
Expand Down Expand Up @@ -96,7 +97,8 @@ export function DeviceStreamView(props: {
}) {
const model = deviceModel(props.platform, props.deviceName ?? "");
const isDuo = model?.id === "iphone-duo";
const [presentation, setPresentation] = useState<"phone" | "flat">("phone");
const presentation = useClientSettings((settings) => settings.deviceView);
const setPresentation = (view: DeviceView) => void updateClientSettings({ deviceView: view });
const [keyboardAttached, setKeyboardAttached] = useState(false);
const cancelPhoneInputRef = useRef<(() => void) | null>(null);
const cancelPhoneInput = () => cancelPhoneInputRef.current?.();
Expand Down Expand Up @@ -150,7 +152,7 @@ export function DeviceStreamView(props: {
props.allowPhoneView &&
(status === "streaming" || retainingFrame) &&
props.visible &&
presentation === "phone" &&
presentation === "3d" &&
!phoneUnavailable &&
!mjpegUrl &&
!props.axOverlay &&
Expand Down Expand Up @@ -281,7 +283,7 @@ export function DeviceStreamView(props: {
},
}
: null,
showPhone: () => setPresentation("phone"),
showPhone: () => setPresentation("3d"),
showFlat: () => setPresentation("flat"),
handle: client
? {
Expand Down Expand Up @@ -433,7 +435,7 @@ export function DeviceStreamView(props: {
aria-pressed={!!showPhone}
disabled={!!phoneUnavailableReason}
title={phoneUnavailableReason ?? "Show 3D phone"}
onClick={() => setPresentation("phone")}
onClick={() => setPresentation("3d")}
>
3D
</Button>
Expand Down
10 changes: 6 additions & 4 deletions docs/user/devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ unless you power it off. Closed tabs stay closed after a reload. To watch the
device again, choose it from **+ → Device**.

Choose **3D view** to inspect supported devices while the live screen stays
interactive. On iPhone Duo, use the fold and stance controls to change its
physical pose, or pinch over the device to adjust the hinge. Turning the model
to the other screen switches the live display and touch input to that screen.
**Restore 3D view** returns the device to a screen-facing position.
interactive. Choose **Flat view** to go back to the plain screen. T3 Code
remembers the last one you picked and uses it for every device. On iPhone Duo,
use the fold and stance controls to change its physical pose, or pinch over the
device to adjust the hinge. Turning the model to the other screen switches the
live display and touch input to that screen. **Restore 3D view** returns the
device to a screen-facing position.
On supported Android foldables, use **Fold device** and **Unfold device** beside
the screen to change its posture in either view.

Expand Down
7 changes: 7 additions & 0 deletions packages/contracts/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ export {
BrowserRecordingFrameRate,
DEFAULT_BROWSER_RECORDING_FRAME_RATE,
} from "./preview.ts";
export const DeviceView = Schema.Literals(["3d", "flat"]);
export type DeviceView = typeof DeviceView.Type;
const DEFAULT_DEVICE_VIEW: DeviceView = "3d";
export const BrowserLinkTarget = Schema.Literals(["system", "app"]);
export type BrowserLinkTarget = typeof BrowserLinkTarget.Type;
export const DEFAULT_BROWSER_LINK_TARGET: BrowserLinkTarget = "system";
Expand Down Expand Up @@ -397,6 +400,9 @@ export const ClientSettingsSchema = Schema.Struct({
Schema.withDecodingDefault(Effect.succeed(DEFAULT_BROWSER_AUTO_SHOW_FLOATING_PREVIEW)),
),

// Remembers the last 3D or flat choice made in the device toolbar.
deviceView: DeviceView.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_DEVICE_VIEW))),

browserProfiles: Schema.Array(BrowserProfile).pipe(
Schema.withDecodingDefault(Effect.succeed([])),
),
Expand Down Expand Up @@ -1666,6 +1672,7 @@ export const ClientSettingsPatch = Schema.Struct({
browserSearchEngine: Schema.optionalKey(BrowserSearchEngine),
browserCustomSearchUrl: Schema.optionalKey(BrowserCustomSearchUrl),
browserAutoShowFloatingPreview: Schema.optionalKey(Schema.Boolean),
deviceView: Schema.optionalKey(DeviceView),
browserProfiles: Schema.optionalKey(Schema.Array(BrowserProfile)),
browserDefaultProfileId: Schema.optionalKey(BrowserProfileId),
confirmQuit: Schema.optionalKey(QuitConfirmationMode),
Expand Down
Loading