Mirror of design_agent_front/design-agent/ for shipping alongside backend.
Vite plugin (vitePluginPhaseZApi) endpoints :
- POST /api/run — spawn `python -m src.phase_z2_pipeline` with overrides
- GET /api/sample-mdx?mdx=03/04/05 — fixed sample MDX
- GET /frame-preview/{n} — figma preview thumbnails
- GET /data/runs/{run_id}/{path} — pipeline artifacts (final.html, step*.json, ...)
Env toggle forward (보고용) :
PHASE_Z_ALLOW_RESTRUCTURE / PHASE_Z_ALLOW_REJECT / PHASE_Z_MAX_RANK=32
Components :
- LeftMdxPanel (03/04/05 fix list + section tree)
- SlideCanvas (iframe + slideOverrideCss prop for inline CSS inject)
- FramePanel (label priority + confidence sort)
- LayoutPanel
README with mermaid diagrams covering the 5-step demo flow.
node_modules / dist / .manus-logs / .env excluded via .gitignore.
156 lines
4.8 KiB
TypeScript
156 lines
4.8 KiB
TypeScript
/**
|
|
* GOOGLE MAPS FRONTEND INTEGRATION - ESSENTIAL GUIDE
|
|
*
|
|
* USAGE FROM PARENT COMPONENT:
|
|
* ======
|
|
*
|
|
* const mapRef = useRef<google.maps.Map | null>(null);
|
|
*
|
|
* <MapView
|
|
* initialCenter={{ lat: 40.7128, lng: -74.0060 }}
|
|
* initialZoom={15}
|
|
* onMapReady={(map) => {
|
|
* mapRef.current = map; // Store to control map from parent anytime, google map itself is in charge of the re-rendering, not react state.
|
|
* </MapView>
|
|
*
|
|
* ======
|
|
* Available Libraries and Core Features:
|
|
* -------------------------------
|
|
* 📍 MARKER (from `marker` library)
|
|
* - Attaches to map using { map, position }
|
|
* new google.maps.marker.AdvancedMarkerElement({
|
|
* map,
|
|
* position: { lat: 37.7749, lng: -122.4194 },
|
|
* title: "San Francisco",
|
|
* });
|
|
*
|
|
* -------------------------------
|
|
* 🏢 PLACES (from `places` library)
|
|
* - Does not attach directly to map; use data with your map manually.
|
|
* const place = new google.maps.places.Place({ id: PLACE_ID });
|
|
* await place.fetchFields({ fields: ["displayName", "location"] });
|
|
* map.setCenter(place.location);
|
|
* new google.maps.marker.AdvancedMarkerElement({ map, position: place.location });
|
|
*
|
|
* -------------------------------
|
|
* 🧭 GEOCODER (from `geocoding` library)
|
|
* - Standalone service; manually apply results to map.
|
|
* const geocoder = new google.maps.Geocoder();
|
|
* geocoder.geocode({ address: "New York" }, (results, status) => {
|
|
* if (status === "OK" && results[0]) {
|
|
* map.setCenter(results[0].geometry.location);
|
|
* new google.maps.marker.AdvancedMarkerElement({
|
|
* map,
|
|
* position: results[0].geometry.location,
|
|
* });
|
|
* }
|
|
* });
|
|
*
|
|
* -------------------------------
|
|
* 📐 GEOMETRY (from `geometry` library)
|
|
* - Pure utility functions; not attached to map.
|
|
* const dist = google.maps.geometry.spherical.computeDistanceBetween(p1, p2);
|
|
*
|
|
* -------------------------------
|
|
* 🛣️ ROUTES (from `routes` library)
|
|
* - Combines DirectionsService (standalone) + DirectionsRenderer (map-attached)
|
|
* const directionsService = new google.maps.DirectionsService();
|
|
* const directionsRenderer = new google.maps.DirectionsRenderer({ map });
|
|
* directionsService.route(
|
|
* { origin, destination, travelMode: "DRIVING" },
|
|
* (res, status) => status === "OK" && directionsRenderer.setDirections(res)
|
|
* );
|
|
*
|
|
* -------------------------------
|
|
* 🌦️ MAP LAYERS (attach directly to map)
|
|
* - new google.maps.TrafficLayer().setMap(map);
|
|
* - new google.maps.TransitLayer().setMap(map);
|
|
* - new google.maps.BicyclingLayer().setMap(map);
|
|
*
|
|
* -------------------------------
|
|
* ✅ SUMMARY
|
|
* - “map-attached” → AdvancedMarkerElement, DirectionsRenderer, Layers.
|
|
* - “standalone” → Geocoder, DirectionsService, DistanceMatrixService, ElevationService.
|
|
* - “data-only” → Place, Geometry utilities.
|
|
*/
|
|
|
|
/// <reference types="@types/google.maps" />
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import { usePersistFn } from "@/hooks/usePersistFn";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
declare global {
|
|
interface Window {
|
|
google?: typeof google;
|
|
}
|
|
}
|
|
|
|
const API_KEY = import.meta.env.VITE_FRONTEND_FORGE_API_KEY;
|
|
const FORGE_BASE_URL =
|
|
import.meta.env.VITE_FRONTEND_FORGE_API_URL ||
|
|
"https://forge.butterfly-effect.dev";
|
|
const MAPS_PROXY_URL = `${FORGE_BASE_URL}/v1/maps/proxy`;
|
|
|
|
function loadMapScript() {
|
|
return new Promise(resolve => {
|
|
const script = document.createElement("script");
|
|
script.src = `${MAPS_PROXY_URL}/maps/api/js?key=${API_KEY}&v=weekly&libraries=marker,places,geocoding,geometry`;
|
|
script.async = true;
|
|
script.crossOrigin = "anonymous";
|
|
script.onload = () => {
|
|
resolve(null);
|
|
script.remove(); // Clean up immediately
|
|
};
|
|
script.onerror = () => {
|
|
console.error("Failed to load Google Maps script");
|
|
};
|
|
document.head.appendChild(script);
|
|
});
|
|
}
|
|
|
|
interface MapViewProps {
|
|
className?: string;
|
|
initialCenter?: google.maps.LatLngLiteral;
|
|
initialZoom?: number;
|
|
onMapReady?: (map: google.maps.Map) => void;
|
|
}
|
|
|
|
export function MapView({
|
|
className,
|
|
initialCenter = { lat: 37.7749, lng: -122.4194 },
|
|
initialZoom = 12,
|
|
onMapReady,
|
|
}: MapViewProps) {
|
|
const mapContainer = useRef<HTMLDivElement>(null);
|
|
const map = useRef<google.maps.Map | null>(null);
|
|
|
|
const init = usePersistFn(async () => {
|
|
await loadMapScript();
|
|
if (!mapContainer.current) {
|
|
console.error("Map container not found");
|
|
return;
|
|
}
|
|
map.current = new window.google.maps.Map(mapContainer.current, {
|
|
zoom: initialZoom,
|
|
center: initialCenter,
|
|
mapTypeControl: true,
|
|
fullscreenControl: true,
|
|
zoomControl: true,
|
|
streetViewControl: true,
|
|
mapId: "DEMO_MAP_ID",
|
|
});
|
|
if (onMapReady) {
|
|
onMapReady(map.current);
|
|
}
|
|
});
|
|
|
|
useEffect(() => {
|
|
init();
|
|
}, [init]);
|
|
|
|
return (
|
|
<div ref={mapContainer} className={cn("w-full h-[500px]", className)} />
|
|
);
|
|
}
|