import { roomAlignmentAction } from "./workflow.mjs"; import { transparencyActions } from "./scene-appearance.mjs"; const AUTHORING_PAGES = ["global", "main", "createTools", "createLight", "lightSettings", "drawTools", "curveTools", "actorSettings", "sceneTools", "sceneMore", "roomTools", "sceneTransparency", "doorSettings", "brushSettings", "suggestions", "recordingTools"]; const item = (id, label, disabled = false) => ({ id, label, disabled }); function authoringMenu(c, page = "main") { if (!c.authoring || c.phase !== "explore") return null; if (!AUTHORING_PAGES.includes(page) && page !== "interaction") return null; const back = item("authoringBack", "Back"), voice = item("voice", c.actorSelected ? "Describe a basic motion" : c.targetName ? "Describe an edit" : "Describe your idea"); const target = c.actorName || c.targetName || (c.objectSelection?.length ? `${c.objectSelection.length} objects selected` : "Current scene"); const door = c.objectSelection?.length === 1 && c.door?.id === c.objectSelection[0]; const regionTarget = c.regionTarget ?? (c.objectSelection?.length === 1 && !c.actorSelected); const result = (title, entries, hint = "X Voice · Y Menu · A Confirm · B Cancel") => ({ title, entries, hint }); if (c.voiceDraft) return result("Review speech · Not sent", [item("voiceConfirm", "Send · A"), item("voiceRetry", "Speak again · X"), item("voiceCancel", "Cancel speech · B")], "Right stick: pages · A Send · X Retry · B Cancel"); if (c.recording && c.recording !== "idle") return result(c.recording === "recording" ? "Listening" : "Processing speech", [item("voice", c.recording === "recording" ? "Stop listening" : "Transcribing…", c.recording !== "recording"), item("voiceCancel", "Cancel speech · B")], "Release X to review · B Cancel"); const p = c.production; if (p?.transitioning || p?.saving) return result("Saving changes", [item("cinemaSave", "Please wait…", true)]); if (p?.requesting) return result("Camera / Lighting Agent", [item("cinemaCancel", "Cancel request")], "B Cancel"); if (p?.placing) return result("Choose a placement point", [item("cinemaCancel", "Cancel placement · B")], p.placement?.visible ? "Place at the ring · A Create · B Cancel" : "Point at visible ground · A Create"); if (p?.dirty) return result(p.tool === "transform" ? "Transform" : p.tool === "curve" ? "Edit curve" : "Preview changes", [item("cinemaSave", "Save · A"), item("cinemaCancel", "Revert · B")], p.tool === "transform" ? "L stick: Move · L grip + stick: Height\nR stick: Rotate · R grip: Grab" : p.tool === "curve" ? "Right grip: pull control points · Release, then A Save" : "A Save · B Discard"); if (page === "sceneTransparency") return result(`Scene transparency · ${Math.round((1 - (c.roomOpacity ?? 0.5)) * 100)}%`, [...transparencyActions(c.roomOpacity), back], "70% is the most transparent · Saved in this browser"); if (page === "roomTools" && c.roomMode && c.hasScene) { const align = roomAlignmentAction(c), blocked = !!(c.editingBusy || c.job || c.saving || c.finishing || c.applying || c.actorPlacing || c.draft?.active || p?.monitor || p?.playing) || (c.videoState ?? "idle") !== "idle"; return result( "Room / Alignment", [align, item("sceneTransparency", "Scene transparency"), item("overview", "Scene overview", blocked), back], blocked ? "Finish the current operation before adjusting the room." : !c.roomAlignmentAvailable ? "Build or load a reconstructed room to align it." : !c.xr || c.xrMode !== "immersive-ar" ? "Enter Quest MR to align the room with your real space." : "Align room opens a preview, even when a saved offset exists. · A Confirm · B Back" ); } if (p && !c.editing) { if (page === "recordingTools") return result("Recording", [item("record", c.videoState === "recording" ? "Stop and save" : "Start recording", !c.productionReady && c.videoState !== "recording"), item("videoFiles", "View recordings"), back]); return result(p.monitor ? `Camera monitor · ${p.cameraName || ""}` : p.previewLabel ? `Rehearse · ${p.previewLabel}` : "Explore mode", [ item(p.monitor ? "cinemaNext" : "cinemaPreview", p.monitor ? "Next camera" : "Camera Agent · View camera"), item(p.playing ? "cinemaPause" : "cinemaPlay", p.playing ? "Pause rehearsal · Left grip" : p.activePreviewIds?.length ? "Resume rehearsal · Left grip" : "Rehearse selection · Left grip", !p.canPreview), ...p.monitor ? [item("cinemaClose", "Close monitor")] : [item("recordingTools", "Recording and export", !c.productionReady)], ...c.script?.agentText || c.script?.userText ? [item("agentReply", "View Agent reply")] : [], ...c.roomMode && c.hasScene ? [item("roomTools", "Room / Alignment")] : [], item("edit", "Enter edit mode") ], p.previewIds?.length ? "Rehearse selection and linked interactions · Right stick click: edit" : "Right stick click: edit and select a target · X Voice"); } if (c.saving || c.finishing || c.applying) return result("Saving", [item("save", "Saving…", true)]); if (c.job === "ready") return result("Preview result", [ item("apply", "Apply changes"), item("discard", "Discard preview"), ...c.script?.agentText || c.script?.userText ? [item("agentReply", "View Agent reply")] : [], ...c.generatedPreview ? [item("motionReplay", "Replay motion")] : c.floodPreview ? [item("floodPreview", "Replay interaction")] : [] ], "A Apply · B Discard · X Voice"); if (c.job === "running") return result("Agent working", [item("cancel", "Stop request"), ...c.script?.agentText || c.script?.userText ? [item("agentReply", "View Agent reply")] : []], "B Stop · X Voice"); if (c.actorPlacing) return result("Place actor", [item("cancelOperation", "Cancel placement")], "Point at ground · A Place · B Cancel"); if (c.draft?.active || c.objectInteraction?.mode === "draft") { const regions = c.draft?.kind === "regions", drawing = !!c.draft?.drawing; if (page === "brushSettings") return result("Brush settings", [ item("draftSmooth", "Smoothing: " + (c.draft?.smoothingLabel || "Standard"), drawing), ...c.draft?.mode === "space3d" ? [item("brushNear", "Bring tip closer", drawing), item("brushFar", "Move tip farther", drawing)] : [], item("draftClear", "Clear draft", drawing), back ]); return result(regions ? "Draw interaction regions" : c.draft?.mode === "space3d" ? "Spatial path · 3D" : "Ground path · 2D", [ item("draftApply", regions ? "Finish regions" : "Save curve", drawing || !(regions ? c.draft?.regionCount : c.draft?.pointCount)), item("draftUndo", regions ? "Undo last region" : "Undo stroke", !c.draft?.canUndo || drawing), ...regions ? [item("regionSurface", c.draft?.forceFloor ? "Draw object sources" : "Draw floor trigger", drawing)] : [], ...!regions ? [item("brushSettings", "Brush settings")] : [], item("draftCancel", "Cancel drawing") ], "Right trigger: draw · Release, then A Save · B Cancel"); } if (p && (page === "global" || page === "main" && !c.objectSelection?.length)) return result("Edit mode", [ item("createTools", "Create"), item("drawTools", "Sketches and paths"), ...c.roomMode && c.hasScene ? [item("roomTools", "Room / Alignment")] : [], ...c.script?.agentText || c.script?.userText ? [item("agentReply", "View Agent reply")] : [], ...c.suggestions?.length ? [item("suggestions", "Agent suggestions")] : [], item("cinemaComplete", "Finish setup and explore"), item("edit", "Return to explore") ], "Right stick: select · A Confirm · B / Y Hide menu"); if (p && page === "createTools") return result("Create", [item("createLight", "Light"), item("cinemaCamera", "Camera"), item("cinemaBox", "Object"), item("actorPlace", "Actor", !c.actorReady), item("cinemaCreateOther", "Other")], "Choose a category · B Back · Edit after placement"); if (p && page === "createLight") return result("Light type", [item("cinemaPoint", "Point · All directions"), item("cinemaSpot", "Spot · Directional"), back], "Choose a type, then a ground point · Type stays editable"); if (p && page === "lightSettings") return result("Light settings", [item("cinemaLightPoint", "Switch to point", c.selectedLightType === "point"), item("cinemaLightSpot", "Switch to spot", c.selectedLightType === "spot"), item("cinemaOther", "Voice: brightness and color"), back], "Review changes · A Save · B Revert"); if (p?.monitor && c.editing) return result(`Camera monitor · ${p.cameraName || ""}`, [item("cinemaAdjustCamera", "Adjust this camera"), item("cinemaNext", "Next camera"), item("cinemaClose", "Close monitor")], "B Close · After saving, say “preview camera”"); if (p && c.objectInteraction && c.selectedLightType && !["drawTools", "curveTools"].includes(page)) return result("Light · Interaction", [item("lightSettings", "Type and light settings"), item("cinemaBind", "Bind selected curve", !c.curveCount), item("cinemaPlay", "Rehearse this light", !p.canPreview), item("interactionClose", "Back")]); if (p && c.objectInteraction && c.selectedCameraId && !["drawTools", "curveTools"].includes(page)) return result("Camera · Interaction", [item("cinemaPreview", "View this camera"), item("cinemaBind", "Bind selected curve", !c.curveCount), item("cinemaOther", "Voice: camera and movement"), item("interactionClose", "Back")]); if (p && c.objectInteraction && page !== "drawTools" && page !== "curveTools") return result("Interaction", [ item("voice", "Describe an interaction"), item("drawTools", "Draw a path or region"), item("cinemaBind", "Bind selected curve", !c.curveCount), item("cinemaPlay", "Rehearse selection · Left grip", !p.canPreview), item("interactionClose", "Back") ]); if (p && page === "main" && c.objectSelection?.length) return result(target, [item("cinemaTransform", "Transform"), item("objectInteraction", "Interaction"), item("cinemaOther", "Other")], "Right stick: select · A Confirm · Other starts voice"); if (page === "suggestions") return result("Agent suggestions", [ ...(c.suggestions || []).map((s, i) => item("suggestion:" + i, s.label)), back ], "Right stick: select · A Confirm · B Back"); if (page === "drawTools") return result("Spatial sketches", [ item("drawFloor", "Draw ground path · 2D"), item("drawSpace", "Draw spatial path · 3D"), ...regionTarget ? [item("regionDraft", "Draw surface sources and trigger region")] : [], ...c.curveCount ? [item("curveTools", `Saved curves · ${c.curveCount}`)] : [], back ]); if (page === "curveTools") return result("Saved curves", [ item("curveNext", "Next curve · " + c.curveName, !c.curveCount), ...p ? [item("cinemaCurveEdit", "Pull control points", !c.curveCount), item("cinemaStraight", "Extreme · Straight endpoints", !c.curveCount), item("cinemaSmooth", "Smooth control points", !c.curveCount)] : [], item("curveRemove", "Delete selected curve", !c.curveCount), back ]); if (page === "recordingTools") return result("Recording and export", [item("record", c.videoState === "recording" ? "Stop and save recording" : c.videoRetry ? "Retry recording save" : "Start recording", ["starting", "saving"].includes(c.videoState)), item("videoFiles", "Saved recordings"), back]); if (page === "actorSettings") return result(target, [ item("objectTransform", "Move and rotate"), item("actors", "Motion assets and entrance"), item("actorClearMotion", "Clear motion"), item("actorRemove", "Delete actor"), item("sceneTools", "Scene tools"), back ]); if (page === "doorSettings") return result(target, [ item("doorToggle", c.door?.effect?.open ? "Close door" : "Open door"), item("doorHinge", "Switch hinge side"), item("doorDirection", "Change opening direction"), item("objectTransform", "Move and rotate"), item("sceneTools", "Scene tools"), back ]); if (page === "sceneTools") return result("Scene tools", [ item("actorPlace", "Add actor", !c.actorReady), ...c.actorCount ? [item("actorTransport", p?.playing ? "Pause rehearsal" : "Rehearse selection", p ? !p.canPreview : !c.actorMotionCount)] : [], item("save", "Save scene"), item("sceneMore", "Recording, display and more"), back ]); if (page === "sceneMore") return result("Scene settings", [ item("recordingTools", c.videoState === "recording" ? "Recording · Stop and save" : "Recording and export"), item("display", "Display and alignment"), item("overview", "Scene overview"), item("authoringHelp", "Controls"), item("selectionSettings", "Selection and materials"), back ]); if (c.suggestions?.length) return result("Agent suggestions", [ ...c.suggestions.map((s, i) => item("suggestion:" + i, s.label)), item("dismissSuggestions", "Dismiss suggestions") ], "Choose a suggestion to preview · Right stick: select · A Confirm"); if (c.actorSelected) return result(target, [item("actorAgent", "Generate a basic motion"), item("drawTools", "Draw a motion path"), item("actorPreview", "Rehearse this actor"), item("actorSettings", "Actor settings")]); if (door) return result(target, [item("voice", "Describe an interaction"), item("regionDraft", "Draw surface sources and trigger region"), ...c.floodAvailable ? [item("floodPreview", "Rehearse interaction")] : [], item("doorSettings", "Door settings")]); return result(target, [ voice, item("drawTools", "Draw a spatial sketch"), ...c.objectSelection?.length ? [item("objectTransform", "Move and rotate")] : [item("edit", c.editing ? "Finish selection" : "Select objects")], item("sceneTools", "Scene tools") ]); } function authoringParent(page) { return { global: "global", main: "global", createTools: "global", sceneTools: "global", roomTools: "global", sceneTransparency: "roomTools", suggestions: "global", createLight: "createTools", lightSettings: "interaction", curveTools: "drawTools", sceneMore: "sceneTools", recordingTools: "sceneMore" }[page] || "main"; } function createMenuNavigation() { const states = /* @__PURE__ */ new WeakMap(); return { sample(source, active) { const y = source.gamepad?.axes?.[3] ?? 0, old = states.get(source); if (!active || source.handedness !== "right") { states.set(source, { active: false, ready: false }); return 0; } const neutral = Math.abs(y) < 0.3; if (!old?.active) { states.set(source, { active: true, ready: neutral }); return 0; } if (neutral) { old.ready = true; return 0; } if (!old.ready || Math.abs(y) < 0.65) return 0; old.ready = false; return Math.sign(y); } }; } const suggestionScope = (c) => JSON.stringify([ c.revision, c.spatialContext?.spatialKey || c.spatialKey || "", [...c.targetIds || c.objectSelection || []].sort(), c.curveId || null ]); export { AUTHORING_PAGES, authoringMenu, authoringParent, createMenuNavigation, suggestionScope };