Skip to content

Commit 0619dfd

Browse files
bennuttallclaude
andcommitted
Land the "Switch to Pi" link straight in Live mode
Switching to the Pi's own copy previously dropped you back in Simulator mode, so you still had to manually toggle to Live and re-type the host. The share-link fragment now carries an optional &live=1 alongside canvas=, which the import effect uses to call setMode('live') immediately after loading the canvas — and the host field already defaults to raspberrypi.local (now exported as DEFAULT_HOST from pi.ts), so the Pi's copy opens ready to click Connect to Pi. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJFnmNcWRtwcDPZQ1vMMyc
1 parent d973704 commit 0619dfd

3 files changed

Lines changed: 19 additions & 14 deletions

File tree

‎src/App.tsx‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,10 @@ function pinsInUse(nodes: DeviceFlowNode[]): Set<number> {
123123
return pins;
124124
}
125125

126-
/** The payload of a `#canvas=<payload>` fragment left by a share link, if any */
127-
function shareHashParam(): string | null {
128-
const match = /^#canvas=(.+)$/.exec(window.location.hash);
129-
return match ? match[1] : null;
126+
/** The payload of a `#canvas=<payload>[&live=1]` fragment left by a share link, if any */
127+
function shareHashParam(): { canvas: string; live: boolean } | null {
128+
const match = /^#canvas=([^&]+)(&live=1)?$/.exec(window.location.hash);
129+
return match ? { canvas: match[1], live: Boolean(match[2]) } : null;
130130
}
131131

132132
/** A canvas name, trimmed to characters safe to use verbatim as a filename stem */
@@ -751,19 +751,22 @@ function Editor() {
751751
// A share link is imported as a new named canvas once decoded (async,
752752
// since the payload is deflate-compressed — see persist.ts); the
753753
// fragment is scrubbed immediately so a refresh doesn't re-import it
754-
// or leak it into browser history beyond this one entry.
754+
// or leak it into browser history beyond this one entry. The `&live=1`
755+
// suffix is how the "Switch to Pi" link (LiveModeModal) asks to land
756+
// straight in Live mode instead of Simulator.
755757
useEffect(() => {
756758
const param = shareHashParam();
757759
if (!param) return;
758760
window.history.replaceState(null, '', window.location.pathname + window.location.search);
759-
decodeSharedCanvas(param).then((imported) => {
761+
decodeSharedCanvas(param.canvas).then((imported) => {
760762
if (!imported) {
761763
showWarning('This share link is invalid or corrupted');
762764
return;
763765
}
764766
const name = untitledCanvasName(listCanvases(), imported.name || 'shared canvas');
765767
saveCanvas(name, imported.nodes, imported.edges);
766768
applyCanvas(name);
769+
if (param.live) setMode('live');
767770
showWarning(`Imported shared canvas as "${name}"`);
768771
});
769772
// mount-only

‎src/components/LiveModeModal.tsx‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
22
import type { Edge } from '@xyflow/react';
33
import type { DeviceFlowNode } from '../types';
44
import { encodeSharedCanvas, SHARE_TOO_LARGE } from '../persist';
5-
import { DEFAULT_APP_PORT } from '../pi';
5+
import { DEFAULT_APP_PORT, DEFAULT_HOST } from '../pi';
66

77
interface Props {
88
onClose: () => void;
@@ -20,15 +20,15 @@ interface StoredAppLink {
2020

2121
function loadStored(): StoredAppLink {
2222
const raw = localStorage.getItem(STORAGE_KEY);
23-
if (!raw) return { host: '', port: DEFAULT_APP_PORT };
23+
if (!raw) return { host: DEFAULT_HOST, port: DEFAULT_APP_PORT };
2424
try {
2525
const parsed = JSON.parse(raw) as Partial<StoredAppLink>;
2626
return {
27-
host: typeof parsed.host === 'string' ? parsed.host : '',
27+
host: typeof parsed.host === 'string' ? parsed.host : DEFAULT_HOST,
2828
port: typeof parsed.port === 'string' ? parsed.port : DEFAULT_APP_PORT,
2929
};
3030
} catch {
31-
return { host: '', port: DEFAULT_APP_PORT };
31+
return { host: DEFAULT_HOST, port: DEFAULT_APP_PORT };
3232
}
3333
}
3434

@@ -39,8 +39,10 @@ function loadStored(): StoredAppLink {
3939
* plain HTTP, where Live mode isn't blocked — so instead of connecting
4040
* a websocket from here, "Switch to Pi" opens the Pi's own copy of the
4141
* app with the current canvas attached as a `#canvas=` share link, in a
42-
* new tab. That's a page navigation, not a connection — the actual
43-
* websocket "Connect to Pi" happens once you're there.
42+
* new tab, plus `&live=1` so it lands straight in Live mode (App.tsx's
43+
* share-import effect) instead of Simulator. That's a page navigation,
44+
* not a connection — the actual websocket "Connect to Pi" happens once
45+
* you're there (host box defaults to raspberrypi.local, same as here).
4446
*/
4547
export function LiveModeModal({ onClose, nodes, edges, canvasName }: Props) {
4648
const [link, setLink] = useState(loadStored);
@@ -67,7 +69,7 @@ export function LiveModeModal({ onClose, nodes, edges, canvasName }: Props) {
6769
const port = link.port.trim() || DEFAULT_APP_PORT;
6870
const href =
6971
host && encoded && encoded !== SHARE_TOO_LARGE
70-
? `http://${host}:${port}/app/#canvas=${encoded}`
72+
? `http://${host}:${port}/app/#canvas=${encoded}&live=1`
7173
: undefined;
7274

7375
return (

‎src/pi.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function liveModeSupported(): boolean {
1818
}
1919

2020
const STORAGE_KEY = 'gpio-webapp.pi-address';
21-
const DEFAULT_HOST = 'raspberrypi.local';
21+
export const DEFAULT_HOST = 'raspberrypi.local';
2222
export const DEFAULT_PORT = '8765';
2323
/** Default port for the *web app* itself when it's served from the Pi
2424
* (python3 -m http.server 8000 — see docs/hosted-deployment.md option 1),

0 commit comments

Comments
 (0)