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
+
>
)}