diff --git a/maps/function-declarations.js b/maps/function-declarations.js index c80b15ca..6c5143e3 100644 --- a/maps/function-declarations.js +++ b/maps/function-declarations.js @@ -57,6 +57,7 @@ export function embed(location) { loading="lazy" allowfullscreen referrerpolicy="no-referrer-when-downgrade" + sandbox="allow-scripts allow-popups allow-forms allow-same-origin allow-popups-to-escape-sandbox" src="https://www.google.com/maps/embed/v1/place?key=${API_KEY} &q=${location}" > diff --git a/maps/index.html b/maps/index.html index db0d00fa..50b30769 100644 --- a/maps/index.html +++ b/maps/index.html @@ -7,6 +7,9 @@
+
+ +
diff --git a/maps/script.js b/maps/script.js index 5649c7f9..90c8ecb4 100644 --- a/maps/script.js +++ b/maps/script.js @@ -70,6 +70,20 @@ async function init() { } else { document.documentElement.setAttribute("data-theme", "light"); } + + const saveLocalButton = document.querySelector("#save-local-button"); + saveLocalButton.addEventListener("click", () => { + const location = document.querySelector("#map iframe").src; + const caption = document.querySelector("#caption p")?.textContent || ""; + const content = `Location: ${location}\nCaption: ${caption}`; + const blob = new Blob([content], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "map-data.txt"; + a.click(); + URL.revokeObjectURL(url); + }); } init(); diff --git a/spatial/src/TopBar.tsx b/spatial/src/TopBar.tsx index b28d9379..77086264 100644 --- a/spatial/src/TopBar.tsx +++ b/spatial/src/TopBar.tsx @@ -13,7 +13,7 @@ // limitations under the License. import { useAtom } from "jotai"; -import { useResetState } from "./hooks"; +import { useResetState, useSaveState } from "./hooks"; import { DetectTypeAtom, HoverEnteredAtom, @@ -25,6 +25,7 @@ import { modelOptions } from "./consts"; export function TopBar() { const resetState = useResetState(); + const saveState = useSaveState(); const [revealOnHover, setRevealOnHoverMode] = useAtom(RevealOnHoverModeAtom); const [detectType] = useAtom(DetectTypeAtom); const [, setHoverEntered] = useAtom(HoverEnteredAtom); @@ -45,6 +46,17 @@ export function TopBar() { >
Reset session
+
{detectType === "2D bounding boxes" ? ( diff --git a/spatial/src/hooks.tsx b/spatial/src/hooks.tsx index c2f44828..8b075f55 100644 --- a/spatial/src/hooks.tsx +++ b/spatial/src/hooks.tsx @@ -19,6 +19,9 @@ import { BumpSessionAtom, ImageSentAtom, PointsAtom, + ImageSrcAtom, + LinesAtom, + DetectTypeAtom, } from "./atoms"; export function useResetState() { @@ -36,3 +39,32 @@ export function useResetState() { setPoints([]); }; } + +export function useSaveState() { + const [imageSrc] = useAtom(ImageSrcAtom); + const [boundingBoxes2D] = useAtom(BoundingBoxes2DAtom); + const [boundingBoxes3D] = useAtom(BoundingBoxes3DAtom); + const [points] = useAtom(PointsAtom); + const [lines] = useAtom(LinesAtom); + const [detectType] = useAtom(DetectTypeAtom); + + return () => { + const state = { + imageSrc, + boundingBoxes2D, + boundingBoxes3D, + points, + lines, + detectType, + }; + + const content = JSON.stringify(state, null, 2); + const blob = new Blob([content], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "spatial-data.json"; + a.click(); + URL.revokeObjectURL(url); + }; +} diff --git a/video/src/App.jsx b/video/src/App.jsx index f904cddd..c16761be 100644 --- a/video/src/App.jsx +++ b/video/src/App.jsx @@ -47,6 +47,28 @@ export default function App() { const isCustomChartMode = isChartMode && chartMode === 'Custom' const hasSubMode = isCustomMode || isChartMode + const saveState = () => { + const state = { + vidUrl, + timecodeList, + selectedMode, + activeMode, + customPrompt, + chartMode, + chartPrompt, + chartLabel, + }; + + const content = JSON.stringify(state, null, 2); + const blob = new Blob([content], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "video-data.json"; + a.click(); + URL.revokeObjectURL(url); + }; + const setTimecodes = ({timecodes}) => setTimecodeList( timecodes.map(t => ({...t, text: t.text.replaceAll("\\'", "'")})) @@ -238,6 +260,12 @@ export default function App() { > ▶️ Generate +
)}