| 1 | import type { BrowserTabView } from "../lib/browserHost"; |
| 2 | import { useBrowserPanelStore } from "../lib/browserPanelStore"; |
| 3 | import { useI18n } from "../lib/i18n"; |
| 4 | import { useState } from "react"; |
| 5 | import { browserElementDrafts } from "../lib/browserElementDrafts"; |
| 6 | |
| 7 | export function BrowserResponsiveControls({ tab }: { tab: BrowserTabView }) { |
| 8 | const chinese = useI18n().locale !== "en"; |
| 9 | const viewport = tab.viewport; |
| 10 | const [picking, setPicking] = useState(false); |
| 11 | const pick = async () => { |
| 12 | const { host, notify } = useBrowserPanelStore.getState(); |
| 13 | const target = browserElementDrafts.getState().target; |
| 14 | if (!target || !host?.pickElement) { notify(chinese ? "当前任务暂不能接收元素附件" : "The current task cannot receive an element attachment yet"); return; } |
| 15 | if (tab.taskId !== "user" && (tab.taskId !== target.taskId || tab.sessionId && tab.sessionId !== target.sessionId)) { notify(chinese ? "请切回此页面所属的任务" : "Return to the task that owns this page"); return; } |
| 16 | setPicking(true); |
| 17 | try { |
| 18 | const selection = await host.pickElement(tab.id); |
| 19 | if (selection) browserElementDrafts.getState().add(target.taskId, target.sessionId, selection); |
| 20 | } catch (error) { notify(String(error)); } |
| 21 | finally { setPicking(false); } |
| 22 | }; |
| 23 | const set = (next: NonNullable<BrowserTabView["viewport"]> | null) => { |
| 24 | const { host, notify } = useBrowserPanelStore.getState(); |
| 25 | void host?.setViewport?.(tab.id, next).catch(error => notify(String(error))); |
| 26 | }; |
| 27 | return <details className="browser-responsive"> |
| 28 | <summary>{chinese ? "响应式" : "Responsive"}{viewport ? ` · ${viewport.width}×${viewport.height}` : ""}</summary> |
| 29 | <div className="browser-responsive__controls"> |
| 30 | {tab.restorePreview && <button type="button" className="btn btn--small" onClick={() => { const { host, notify } = useBrowserPanelStore.getState(); void host?.restorePreview?.(tab.id).catch(error => notify(String(error))); }}>{chinese ? "重新授权并打开预览" : "Reauthorize and reopen preview"}</button>} |
| 31 | <button type="button" className="btn btn--small" disabled={picking} onClick={() => void pick()}>{picking ? (chinese ? "点击元素 · Esc 退出" : "Click an element · Esc to cancel") : (chinese ? "选择元素加入聊天" : "Select element for chat")}</button> |
| 32 | <select aria-label={chinese ? "视口预设" : "Viewport preset"} value={viewport ? `${viewport.width}x${viewport.height}` : "natural"} onChange={event => { |
| 33 | if (event.target.value === "natural") set(null); |
| 34 | else if (event.target.value !== "custom") { const [width, height] = event.target.value.split("x").map(Number); set({ width, height, scale: viewport?.scale ?? "fit" }); } |
| 35 | }}> |
| 36 | <option value="natural">{chinese ? "自然尺寸" : "Natural"}</option> |
| 37 | <option value="393x852">{chinese ? "手机" : "Phone"} 393×852</option> |
| 38 | <option value="768x1024">{chinese ? "平板" : "Tablet"} 768×1024</option> |
| 39 | <option value="1280x720">{chinese ? "桌面" : "Desktop"} 1280×720</option> |
| 40 | {viewport && !["393x852", "768x1024", "1280x720"].includes(`${viewport.width}x${viewport.height}`) && <option value={`${viewport.width}x${viewport.height}`}>{chinese ? "自定义" : "Custom"}</option>} |
| 41 | </select> |
| 42 | <form key={`${tab.id}:${viewport?.width}:${viewport?.height}`} onSubmit={event => { |
| 43 | event.preventDefault(); const data = new FormData(event.currentTarget); |
| 44 | set({ width: Number(data.get("width")), height: Number(data.get("height")), scale: viewport?.scale ?? "fit" }); |
| 45 | }}> |
| 46 | <input name="width" aria-label={chinese ? "CSS 宽度" : "CSS width"} type="number" min="320" max="3840" defaultValue={viewport?.width ?? 1280} required /> × |
| 47 | <input name="height" aria-label={chinese ? "CSS 高度" : "CSS height"} type="number" min="320" max="2160" defaultValue={viewport?.height ?? 720} required /> |
| 48 | <button type="submit" className="btn btn--small">{chinese ? "应用" : "Apply"}</button> |
| 49 | </form> |
| 50 | {viewport && <> |
| 51 | <button type="button" className="btn btn--small" disabled={viewport.width > 2160} onClick={() => set({ ...viewport, width: viewport.height, height: viewport.width })}>{chinese ? "旋转" : "Rotate"}</button> |
| 52 | <select aria-label={chinese ? "显示比例" : "Display scale"} value={viewport.scale} onChange={event => set({ ...viewport, scale: event.target.value === "fit" ? "fit" : Number(event.target.value) })}> |
| 53 | <option value="fit">Fit</option>{[0.5, 0.75, 1, 1.25, 1.5, 2].map(scale => <option key={scale} value={scale}>{scale * 100}%</option>)} |
| 54 | </select> |
| 55 | </>} |
| 56 | </div> |
| 57 | </details>; |
| 58 | } |
| 59 |