mirror of
https://github.com/geoffsee/open-gsio.git
synced 2025-09-08 22:56:46 +00:00
sweet lander
This commit is contained in:

committed by
Geoff Seemueller

parent
818e0e672a
commit
c26d2467f4
5
bun.lock
5
bun.lock
@@ -74,7 +74,6 @@
|
||||
"react-map-gl": "^8.0.4",
|
||||
"react-streaming": "^0.4.2",
|
||||
"react-textarea-autosize": "^8.5.5",
|
||||
"react-use-pwa-install": "^1.0.3",
|
||||
"shiki": "^1.24.0",
|
||||
"tslog": "^4.9.3",
|
||||
"typescript": "^5.7.2",
|
||||
@@ -1646,8 +1645,6 @@
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"pwa-install-handler": ["pwa-install-handler@2.6.2", "", {}, "sha512-9hMpqWNxGZx4ZoBe9k9gHkdZC/d/mvMJLA08FCVVMxOhwHBNuQVzb0DwH8ffEaqFvqu7GaotcvYgGNT1yVWduQ=="],
|
||||
|
||||
"qrcode.react": ["qrcode.react@4.2.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA=="],
|
||||
|
||||
"quansync": ["quansync@0.2.10", "", {}, "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A=="],
|
||||
@@ -1688,8 +1685,6 @@
|
||||
|
||||
"react-textarea-autosize": ["react-textarea-autosize@8.5.9", "", { "dependencies": { "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", "use-latest": "^1.2.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A=="],
|
||||
|
||||
"react-use-pwa-install": ["react-use-pwa-install@1.0.3", "", { "dependencies": { "pwa-install-handler": "^2.6.2" }, "peerDependencies": { "react": "18 || 19" } }, "sha512-poF5teATOCblAchP61+Hx/FIQJtSkjGFcZsJjiyXmG9SfmJWkj8M890lXKlu6QPg/bmG6GE3d+KP3aEO9ehgDw=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
|
||||
|
Submodule crates/yachtpit updated: 44081ad73d...348f20641c
@@ -22,9 +22,10 @@ export class AssistantSdk {
|
||||
const currentTime = `${now.getHours()}:${formattedMinutes} ${now.getSeconds()}s`;
|
||||
|
||||
return `# Assistant Knowledge
|
||||
## Assistant Name
|
||||
### yachtpit-ai
|
||||
## Current Context
|
||||
### Date: ${currentDate} ${currentTime}
|
||||
### Web Host: open-gsio.seemueller.workers.dev
|
||||
${maxTokens ? `### Max Response Length: ${maxTokens} tokens (maximum)` : ''}
|
||||
### Lexicographical Format: Markdown
|
||||
### User Location: ${userLocation || 'Unknown'}
|
||||
|
@@ -58,7 +58,6 @@
|
||||
"react-map-gl": "^8.0.4",
|
||||
"react-streaming": "^0.4.2",
|
||||
"react-textarea-autosize": "^8.5.5",
|
||||
"react-use-pwa-install": "^1.0.3",
|
||||
"shiki": "^1.24.0",
|
||||
"tslog": "^4.9.3",
|
||||
"typescript": "^5.7.2",
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import { IconButton } from '@chakra-ui/react';
|
||||
import { HardDriveDownload } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { usePWAInstall } from 'react-use-pwa-install';
|
||||
|
||||
import { toolbarButtonZIndex } from './toolbar/Toolbar.tsx';
|
||||
|
||||
function InstallButton() {
|
||||
const install = usePWAInstall();
|
||||
// const install = usePWAInstall();
|
||||
|
||||
// <button onClick={handleInstall}>Install App</button>;
|
||||
const install = () => {
|
||||
console.warn('this does not work in all browsers');
|
||||
};
|
||||
return (
|
||||
<IconButton
|
||||
aria-label="Install App"
|
||||
|
@@ -28,7 +28,7 @@ const Chat = observer(({ height, width }) => {
|
||||
<GridItem
|
||||
overflow="auto"
|
||||
width="100%"
|
||||
maxH="100%"
|
||||
maxH="100vh"
|
||||
ref={scrollRef}
|
||||
// If there are attachments, use "100px". Otherwise, use "128px" on Android, "73px" elsewhere.
|
||||
pb={isAndroid ? '128px' : '73px'}
|
||||
|
@@ -22,7 +22,7 @@ const ChatInput = observer(() => {
|
||||
const [shouldFollow, setShouldFollow] = useState<boolean>(userOptionsStore.followModeEnabled);
|
||||
const [couldFollow, setCouldFollow] = useState<boolean>(chatStore.isLoading);
|
||||
|
||||
const [inputWidth, setInputWidth] = useState<string>('50%');
|
||||
const [inputWidth, setInputWidth] = useState<string>('40%');
|
||||
|
||||
useEffect(() => {
|
||||
setShouldFollow(chatStore.isLoading && userOptionsStore.followModeEnabled);
|
||||
@@ -64,10 +64,10 @@ const ChatInput = observer(() => {
|
||||
};
|
||||
|
||||
const inputMaxWidth = useBreakpointValue(
|
||||
{ base: '50rem', lg: '50rem', md: '80%', sm: '100vw' },
|
||||
{ base: '30rem', lg: '50rem', md: '80%', sm: '100vw' },
|
||||
{ ssr: true },
|
||||
);
|
||||
const inputMinWidth = useBreakpointValue({ lg: '40rem' }, { ssr: true });
|
||||
const inputMinWidth = useBreakpointValue({ lg: '40rem', md: '30rem' }, { ssr: true });
|
||||
|
||||
useEffect(() => {
|
||||
setInputWidth('100%');
|
||||
@@ -75,9 +75,7 @@ const ChatInput = observer(() => {
|
||||
|
||||
return (
|
||||
<Box
|
||||
width={inputWidth}
|
||||
maxW={inputMaxWidth}
|
||||
minWidth={inputMinWidth}
|
||||
width={inputMinWidth}
|
||||
mx="auto"
|
||||
p={2}
|
||||
pl={2}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Box, chakra, InputGroup } from '@chakra-ui/react';
|
||||
import { Box, chakra, InputGroup, useBreakpointValue } from '@chakra-ui/react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import AutoResize from 'react-textarea-autosize';
|
||||
@@ -19,7 +19,7 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
|
||||
|
||||
useEffect(() => {
|
||||
if (value.length > 10) {
|
||||
setHeightConstraint();
|
||||
setHeightConstraint(parseInt(value));
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
@@ -38,6 +38,7 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
height={heightConstraint}
|
||||
maxH={heightConstraint}
|
||||
autoFocus
|
||||
onChange={e => onChange(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
@@ -48,8 +49,14 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
|
||||
color="text.primary"
|
||||
borderRadius="20px"
|
||||
border="none"
|
||||
placeholder="Free my mind..."
|
||||
_placeholder={{ color: 'gray.400' }}
|
||||
placeholder="To Gilligan's island!"
|
||||
_placeholder={{
|
||||
color: 'gray.400',
|
||||
textWrap: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
width: '90%',
|
||||
}}
|
||||
_focus={{
|
||||
outline: 'none',
|
||||
}}
|
||||
|
@@ -9,7 +9,7 @@ export function formatConversationMarkdown(messages: Instance<typeof IMessage>[]
|
||||
if (message.role === 'user') {
|
||||
return `**You**: ${message.content}`;
|
||||
} else if (message.role === 'assistant') {
|
||||
return `**Geoff's AI**: ${message.content}`;
|
||||
return `**yachtpit-ai**: ${message.content}`;
|
||||
}
|
||||
return '';
|
||||
})
|
||||
|
@@ -51,7 +51,7 @@ const MessageBubble = observer(({ msg, scrollRef }) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const isUser = msg.role === 'user';
|
||||
const senderName = isUser ? 'You' : "Geoff's AI";
|
||||
const senderName = isUser ? 'You' : 'yachtpit-ai';
|
||||
const isLoading = !msg.content || !(msg.content.trim().length > 0);
|
||||
const messageRef = useRef();
|
||||
|
||||
|
@@ -104,7 +104,7 @@ describe('MessageBubble', () => {
|
||||
it('should render assistant message correctly', () => {
|
||||
render(<MessageBubble msg={mockAssistantMessage} scrollRef={mockScrollRef} />);
|
||||
|
||||
expect(screen.getByText("Geoff's AI")).toBeInTheDocument();
|
||||
expect(screen.getByText('yachtpit-ai')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('message-content')).toHaveTextContent('Assistant response');
|
||||
});
|
||||
|
||||
|
25
packages/client/src/components/contexts/ComponentContext.tsx
Normal file
25
packages/client/src/components/contexts/ComponentContext.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
|
||||
type ComponentContextType = {
|
||||
enabledComponent: string;
|
||||
setEnabledComponent: (component: string) => void;
|
||||
};
|
||||
|
||||
const ComponentContext = createContext<ComponentContextType>({
|
||||
enabledComponent: '',
|
||||
setEnabledComponent: () => {},
|
||||
});
|
||||
|
||||
export const useComponent = () => useContext(ComponentContext);
|
||||
|
||||
export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [enabledComponent, setEnabledComponent] = useState<string>('');
|
||||
|
||||
return (
|
||||
<ComponentContext.Provider value={{ enabledComponent, setEnabledComponent }}>
|
||||
{children}
|
||||
</ComponentContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComponentContext;
|
@@ -1,4 +1,4 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { Box, useBreakpointValue } from '@chakra-ui/react';
|
||||
import React, { memo, useEffect, useMemo } from 'react';
|
||||
|
||||
export interface BevySceneProps {
|
||||
@@ -14,6 +14,8 @@ const BevySceneInner: React.FC<BevySceneProps> = ({
|
||||
glow = false,
|
||||
visible,
|
||||
}) => {
|
||||
const maxWidth = useBreakpointValue({ base: 640, md: 720 }, { ssr: true });
|
||||
|
||||
/* initialise once */
|
||||
useEffect(() => {
|
||||
let dispose: (() => void) | void;
|
||||
@@ -31,7 +33,8 @@ const BevySceneInner: React.FC<BevySceneProps> = ({
|
||||
() => ({
|
||||
position: 'absolute' as const,
|
||||
inset: 0,
|
||||
zIndex: 0,
|
||||
zIndex: 1,
|
||||
maxWidth: maxWidth,
|
||||
opacity: visible ? Math.min(Math.max(intensity, 0), 1) : 0,
|
||||
filter: glow ? 'blur(1px)' : 'none',
|
||||
transition: `opacity ${speed}s ease-in-out`,
|
||||
@@ -42,7 +45,12 @@ const BevySceneInner: React.FC<BevySceneProps> = ({
|
||||
|
||||
return (
|
||||
<Box as="div" sx={wrapperStyles}>
|
||||
<canvas id="yachtpit-canvas" width={1280} height={720} aria-hidden />
|
||||
<canvas
|
||||
id="yachtpit-canvas"
|
||||
width={useBreakpointValue({ base: 640, md: 1280 }, { ssr: true })}
|
||||
height={useBreakpointValue({ base: 360, md: 720 }, { ssr: true })}
|
||||
aria-hidden
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@@ -1,20 +1,30 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { useComponent } from '../contexts/ComponentContext.tsx';
|
||||
|
||||
import { BevyScene } from './BevyScene.tsx';
|
||||
import Map from './Map.tsx';
|
||||
import Tweakbox from './Tweakbox.tsx';
|
||||
|
||||
export const LandingComponent: React.FC = () => {
|
||||
const [speed, setSpeed] = useState(0.2);
|
||||
const [intensity, setIntensity] = useState(0.5);
|
||||
const [particles, setParticles] = useState(false);
|
||||
const [intensity, setIntensity] = useState(0.99);
|
||||
const [glow, setGlow] = useState(false);
|
||||
const [matrixRain, setMatrixRain] = useState(false);
|
||||
const [bevyScene, setBevyScene] = useState(true);
|
||||
const [mapActive, setMapActive] = useState(false);
|
||||
const [mapActive, setMapActive] = useState(true);
|
||||
const [aiActive, setAiActive] = useState(false);
|
||||
|
||||
const map = <Map visible={mapActive} />;
|
||||
const component = useComponent();
|
||||
const { setEnabledComponent } = component;
|
||||
|
||||
useEffect(() => {
|
||||
if (mapActive) {
|
||||
setEnabledComponent('gpsmap');
|
||||
}
|
||||
if (aiActive) {
|
||||
setEnabledComponent('ai');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -36,7 +46,7 @@ export const LandingComponent: React.FC = () => {
|
||||
<Tweakbox
|
||||
sliders={{
|
||||
intensity: {
|
||||
value: !particles ? intensity : 0.99,
|
||||
value: intensity,
|
||||
onChange: setIntensity,
|
||||
label: 'Brightness',
|
||||
min: 0.01,
|
||||
@@ -49,11 +59,6 @@ export const LandingComponent: React.FC = () => {
|
||||
bevyScene: {
|
||||
value: bevyScene,
|
||||
onChange(enabled) {
|
||||
if (enabled) {
|
||||
setParticles(!enabled);
|
||||
setMatrixRain(!enabled);
|
||||
setMapActive(!enabled);
|
||||
}
|
||||
setBevyScene(enabled);
|
||||
},
|
||||
label: 'Instruments',
|
||||
@@ -62,19 +67,32 @@ export const LandingComponent: React.FC = () => {
|
||||
value: mapActive,
|
||||
onChange(enabled) {
|
||||
if (enabled) {
|
||||
setParticles(!enabled);
|
||||
setMatrixRain(!enabled);
|
||||
setBevyScene(!enabled);
|
||||
setEnabledComponent('gpsmap');
|
||||
setAiActive(false);
|
||||
} else {
|
||||
setEnabledComponent('');
|
||||
}
|
||||
setMapActive(enabled);
|
||||
},
|
||||
label: 'Map',
|
||||
},
|
||||
AI: {
|
||||
value: aiActive,
|
||||
onChange(enabled) {
|
||||
if (enabled) {
|
||||
setEnabledComponent('ai');
|
||||
setMapActive(false);
|
||||
} else {
|
||||
setEnabledComponent('');
|
||||
}
|
||||
setAiActive(enabled);
|
||||
},
|
||||
label: 'AI',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<BevyScene speed={speed} intensity={intensity} glow={glow} visible={bevyScene} />
|
||||
{mapActive && map}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@@ -3,6 +3,8 @@ import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import { Box, HStack, Button, Input, Center } from '@chakra-ui/react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
import MapNext from './MapNext.tsx';
|
||||
|
||||
// Types for bevy_flurx_ipc communication
|
||||
interface GpsPosition {
|
||||
latitude: number;
|
||||
@@ -33,64 +35,27 @@ const key =
|
||||
'cGsuZXlKMUlqb2laMlZ2Wm1aelpXVWlMQ0poSWpvaVkycDFOalo0YkdWNk1EUTRjRE41YjJnNFp6VjNNelp6YXlKOS56LUtzS1l0X3VGUGdCSDYwQUFBNFNn';
|
||||
|
||||
function Map(props: { visible: boolean }) {
|
||||
const [mapboxToken, setMapboxToken] = useState(atob(key));
|
||||
const [isTokenLoading, setIsTokenLoading] = useState(false);
|
||||
const [authenticated, setAuthenticated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setAuthenticated(true);
|
||||
setIsTokenLoading(false);
|
||||
}, []);
|
||||
|
||||
const [mapView, setMapView] = useState({
|
||||
longitude: -122.4,
|
||||
latitude: 37.8,
|
||||
zoom: 14,
|
||||
});
|
||||
|
||||
const handleNavigationClick = useCallback(async () => {
|
||||
console.log('handling navigation in map');
|
||||
}, []);
|
||||
|
||||
const handleSearchClick = useCallback(async () => {
|
||||
console.log('handling click search in map');
|
||||
}, []);
|
||||
|
||||
const handleMapViewChange = useCallback(async (evt: any) => {
|
||||
const { longitude, latitude, zoom } = evt.viewState;
|
||||
setMapView({ longitude, latitude, zoom });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
p={4}
|
||||
height="80%"
|
||||
width="100%"
|
||||
position="relative"
|
||||
display={props.visible ? undefined : 'none'}
|
||||
>
|
||||
<Box width={'100%'} height={'100%'} position="relative" zIndex={0}>
|
||||
{/* Map itself */}
|
||||
{authenticated && (
|
||||
<ReactMap
|
||||
mapboxAccessToken={mapboxToken}
|
||||
initialViewState={mapView}
|
||||
onMove={handleMapViewChange}
|
||||
mapStyle="mapbox://styles/mapbox/dark-v11"
|
||||
attributionControl={false}
|
||||
style={{ width: '100%', height: '100%' }} // let the wrapper dictate size
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
/* Full-screen wrapper — fills the viewport and becomes the positioning context */
|
||||
<Box w="100%" h="100vh" position="relative" overflow="hidden">
|
||||
{/* Button bar — absolutely positioned inside the wrapper */}
|
||||
<HStack position="relative" top={1} right={4} zIndex={1} justify={'right'}>
|
||||
<Button size="sm" variant="solid" onClick={handleNavigationClick}>
|
||||
Navigation
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" onClick={handleSearchClick}>
|
||||
Search
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
<MapNext mapboxPublicKey={atob(key)} />
|
||||
{/*<Map*/}
|
||||
{/* mapboxAccessToken={atob(key)}*/}
|
||||
{/* initialViewState={mapView}*/}
|
||||
{/* onMove={handleMapViewChange}*/}
|
||||
{/* mapStyle="mapbox://styles/mapbox/dark-v11"*/}
|
||||
{/* reuseMaps*/}
|
||||
{/* attributionControl={false}*/}
|
||||
{/* style={{width: '100%', height: '100%'}} // let the wrapper dictate size*/}
|
||||
{/*>*/}
|
||||
{/* /!*{vesselPosition && (*!/*/}
|
||||
{/* /!* <Source id="vessel-data" type="geojson" data={vesselGeojson}>*!/*/}
|
||||
{/* /!* <Layer {...vesselLayerStyle} />*!/*/}
|
||||
{/* /!* </Source>*!/*/}
|
||||
{/* /!*)}*!/*/}
|
||||
{/*</Map>*/}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
173
packages/client/src/components/landing-component/MapNext.tsx
Normal file
173
packages/client/src/components/landing-component/MapNext.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import { Box, Button, HStack, Input } from '@chakra-ui/react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import Map, {
|
||||
FullscreenControl,
|
||||
GeolocateControl,
|
||||
Marker,
|
||||
NavigationControl,
|
||||
Popup,
|
||||
ScaleControl,
|
||||
} from 'react-map-gl/mapbox';
|
||||
|
||||
import PORTS from './nautical-base-data.json';
|
||||
import Pin from './pin';
|
||||
|
||||
export default function MapNext(props: any = { mapboxPublicKey: '' } as any) {
|
||||
const [popupInfo, setPopupInfo] = useState(null);
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||
const [isTokenLoading, setIsTokenLoading] = useState(false);
|
||||
const [authenticated, setAuthenticated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setAuthenticated(true);
|
||||
setIsTokenLoading(false);
|
||||
}, []);
|
||||
|
||||
const [mapView, setMapView] = useState({
|
||||
longitude: -122.4,
|
||||
latitude: 37.8,
|
||||
zoom: 14,
|
||||
});
|
||||
|
||||
const handleNavigationClick = useCallback(async () => {
|
||||
console.log('handling navigation in map');
|
||||
}, []);
|
||||
|
||||
const handleSearchClick = useCallback(async () => {
|
||||
console.log('handling click search in map');
|
||||
}, []);
|
||||
|
||||
const handleMapViewChange = useCallback(async (evt: any) => {
|
||||
const { longitude, latitude, zoom } = evt.viewState;
|
||||
setMapView({ longitude, latitude, zoom });
|
||||
}, []);
|
||||
|
||||
const pins = useMemo(
|
||||
() =>
|
||||
PORTS.map((city, index) => (
|
||||
<Marker
|
||||
key={`marker-${index}`}
|
||||
longitude={city.longitude}
|
||||
latitude={city.latitude}
|
||||
anchor="bottom"
|
||||
onClick={e => {
|
||||
// If we let the click event propagates to the map, it will immediately close the popup
|
||||
// with `closeOnClick: true`
|
||||
e.originalEvent.stopPropagation();
|
||||
/*
|
||||
src/MapNext.tsx:34:38 - error TS2345: Argument of type '{ city: string; population: string; image: string; state: string; latitude: number; longitude: number; }' is not assignable to parameter of type 'SetStateAction<null>'.
|
||||
Type '{ city: string; population: string; image: string; state: string; latitude: number; longitude: number; }' provides no match for the signature '(prevState: null): null'.
|
||||
*/
|
||||
// @ts-ignore
|
||||
setPopupInfo(city);
|
||||
}}
|
||||
>
|
||||
<Pin />
|
||||
</Marker>
|
||||
)),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box justifySelf={'right'} w={'100%'}>
|
||||
{/*<HStack position="absolute" top={4} right={4} zIndex={1}>*/}
|
||||
{/* <Box display="flex" alignItems="center">*/}
|
||||
{/* <Button colorScheme="teal" size="sm" variant="solid" onClick={handleSearchClick} mr={2}>*/}
|
||||
{/* Search*/}
|
||||
{/* </Button>*/}
|
||||
{/* {isSearchOpen && (*/}
|
||||
{/* <Box*/}
|
||||
{/* w="200px"*/}
|
||||
{/* transition="all 0.3s"*/}
|
||||
{/* transform={`translateX(${isSearchOpen ? '0' : '100%'})`}*/}
|
||||
{/* opacity={isSearchOpen ? 1 : 0}*/}
|
||||
{/* color="white"*/}
|
||||
{/* >*/}
|
||||
{/* <Input*/}
|
||||
{/* placeholder="Search..."*/}
|
||||
{/* size="sm"*/}
|
||||
{/* _placeholder={{*/}
|
||||
{/* color: '#d1cfcf',*/}
|
||||
{/* }}*/}
|
||||
{/* />*/}
|
||||
{/* </Box>*/}
|
||||
{/* )}*/}
|
||||
{/* </Box>*/}
|
||||
{/* <Button colorScheme="blue" size="sm" variant="solid" onClick={handleNavigationClick}>*/}
|
||||
{/* Layer*/}
|
||||
{/* </Button>*/}
|
||||
{/*</HStack>*/}
|
||||
<Map
|
||||
initialViewState={{
|
||||
latitude: 40,
|
||||
longitude: -100,
|
||||
zoom: 3.5,
|
||||
bearing: 0,
|
||||
pitch: 0,
|
||||
}}
|
||||
mapStyle="mapbox://styles/geoffsee/cmd1qz39x01ga01qv5acea02y"
|
||||
attributionControl={false}
|
||||
mapboxAccessToken={props.mapboxPublicKey}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
// height: '50%',
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
}}
|
||||
>
|
||||
<GeolocateControl position="top-left" />
|
||||
<FullscreenControl position="top-left" />
|
||||
<NavigationControl position="top-left" />
|
||||
<ScaleControl position="top-left" />
|
||||
|
||||
{pins}
|
||||
|
||||
{popupInfo && (
|
||||
<Popup
|
||||
anchor="top"
|
||||
/*
|
||||
src/MapNext.tsx:66:53 - error TS2339: Property 'longitude' does not exist on type 'never'.
|
||||
|
||||
66 longitude={Number(popupInfo.longitude)}
|
||||
*/
|
||||
// @ts-ignore
|
||||
longitude={Number(popupInfo.longitude)}
|
||||
/*
|
||||
src/MapNext.tsx:67:52 - error TS2339: Property 'latitude' does not exist on type 'never'.
|
||||
|
||||
67 latitude={Number(popupInfo.latitude)}
|
||||
~~~~~~~~
|
||||
*/
|
||||
// @ts-ignore
|
||||
latitude={Number(popupInfo.latitude)}
|
||||
onClose={() => setPopupInfo(null)}
|
||||
>
|
||||
<div style={{ color: 'black' }}>
|
||||
{/*src/MapNext.tsx:71:40 - error TS2339: Property 'city' does not exist on type 'never'.
|
||||
|
||||
71 {popupInfo.city}, {popupInfo.state} |{' '}
|
||||
~~~~*/}
|
||||
{/*@ts-ignore*/}
|
||||
{/*@ts-ignore*/}
|
||||
{popupInfo.city},{popupInfo.state}
|
||||
{/*@ts-ignore*/}
|
||||
</div>
|
||||
{/*@ts-ignore*/}
|
||||
<img width="100%" src={popupInfo.image} />
|
||||
<br />
|
||||
<a
|
||||
style={{ color: 'blue' }}
|
||||
target="_new"
|
||||
href={`http://en.wikipedia.org/w/index.php?title=Special:Search&search=${(popupInfo as any).city}, ${(popupInfo as any).state}`}
|
||||
>
|
||||
Wikipedia
|
||||
</a>
|
||||
</Popup>
|
||||
)}
|
||||
</Map>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
import * as React from 'react';
|
||||
|
||||
function ControlPanel() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<p>
|
||||
Data source:{' '}
|
||||
<a href="https://en.wikipedia.org/wiki/List_of_United_States_cities_by_population">
|
||||
Wikipedia
|
||||
</a>
|
||||
</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/8.0-release/examples/mapbox/controls"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{"city":"New York","population":"8,335,897","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Above_Gotham.jpg/240px-Above_Gotham.jpg","state":"New York","latitude":40.7128,"longitude":-74.0060},
|
||||
{"city":"Los Angeles","population":"3,822,238","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/57/LA_Skyline_Mountains2.jpg/240px-LA_Skyline_Mountains2.jpg","state":"California","latitude":34.0522,"longitude":-118.2437},
|
||||
{"city":"Long Beach","population":"456,062","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Long_Beach_skyline_from_Shoreline_Village.jpg/240px-Long_Beach_skyline_from_Shoreline_Village.jpg","state":"California","latitude":33.7701,"longitude":-118.1937},
|
||||
{"city":"Seattle","population":"749,256","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/SeattleI5Skyline.jpg/240px-SeattleI5Skyline.jpg","state":"Washington","latitude":47.6062,"longitude":-122.3321},
|
||||
{"city":"San Francisco","population":"808,437","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/San_Francisco_skyline_from_Coit_Tower.jpg/240px-San_Francisco_skyline_from_Coit_Tower.jpg","state":"California","latitude":37.7749,"longitude":-122.4194},
|
||||
{"city":"San Diego","population":"1,386,932","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/US_Navy_110604-N-NS602-574_Navy_and_Marine_Corps_personnel%2C_along_with_community_leaders_from_the_greater_San_Diego_area_come_together_to_commemora.jpg/240px-US_Navy_110604-N-NS602-574_Navy_and_Marine_Corps_personnel%2C_along_with_community_leaders_from_the_greater_San_Diego_area_come_together_to_commemora.jpg","state":"California","latitude":32.7157,"longitude":-117.1611},
|
||||
{"city":"Norfolk","population":"235,089","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6e/Norfolk_Skyline_from_Portsmouth.jpg/240px-Norfolk_Skyline_from_Portsmouth.jpg","state":"Virginia","latitude":36.8508,"longitude":-76.2859},
|
||||
{"city":"Miami","population":"449,514","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/Miami_skyline_201807_cat.jpg/240px-Miami_skyline_201807_cat.jpg","state":"Florida","latitude":25.7617,"longitude":-80.1918},
|
||||
{"city":"Boston","population":"675,647","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Boston_skyline_and_Boston_Harbor.jpg/240px-Boston_skyline_and_Boston_Harbor.jpg","state":"Massachusetts","latitude":42.3601,"longitude":-71.0589},
|
||||
{"city":"Baltimore","population":"585,708","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Baltimore_Skyline.jpg/240px-Baltimore_Skyline.jpg","state":"Maryland","latitude":39.2904,"longitude":-76.6122},
|
||||
{"city":"Charleston","population":"151,612","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/Charleston_SC_Skyline.jpg/240px-Charleston_SC_Skyline.jpg","state":"South Carolina","latitude":32.7765,"longitude":-79.9311},
|
||||
{"city":"Savannah","population":"147,780","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Savannah_GA%2C_River_Street.jpg/240px-Savannah_GA%2C_River_Street.jpg","state":"Georgia","latitude":32.0809,"longitude":-81.0912},
|
||||
{"city":"Tampa","population":"403,364","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Tampa_skyline_from_South%2C_2022.jpg/240px-Tampa_skyline_from_South%2C_2022.jpg","state":"Florida","latitude":27.9506,"longitude":-82.4572},
|
||||
{"city":"Mobile","population":"187,041","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/7/70/Mobile_skyline_from_Mobile_River.jpg/240px-Mobile_skyline_from_Mobile_River.jpg","state":"Alabama","latitude":30.6954,"longitude":-88.0399},
|
||||
{"city":"Anchorage","population":"288,121","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Anchorage_skyline_and_susitna.jpg/240px-Anchorage_skyline_and_susitna.jpg","state":"Alaska","latitude":61.2181,"longitude":-149.9003},
|
||||
{"city":"Portland","population":"68,408","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/48/Portland_Maine_skyline.jpg/240px-Portland_Maine_skyline.jpg","state":"Maine","latitude":43.6591,"longitude":-70.2568},
|
||||
{"city":"Honolulu","population":"349,547","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Honolulu_and_Diamond_Head.jpg/240px-Honolulu_and_Diamond_Head.jpg","state":"Hawaii","latitude":21.3069,"longitude":-157.8583},
|
||||
{"city":"New Orleans","population":"376,971","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/New_Orleans_skyline_sunset_Dec_28_2021_PANO_DSC07177-07179.jpg/240px-New_Orleans_skyline_sunset_Dec_28_2021_PANO_DSC07177-07179.jpg","state":"Louisiana","latitude":29.9511,"longitude":-90.0715},
|
||||
{"city":"Jacksonville","population":"971,319","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Skyline_of_Jacksonville_FL%2C_South_view_20160706_1.jpg/240px-Skyline_of_Jacksonville_FL%2C_South_view_20160706_1.jpg","state":"Florida","latitude":30.3322,"longitude":-81.6557},
|
||||
{"city":"Houston","population":"2,302,878","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/60/Aerial_views_of_the_Houston%2C_Texas%2C_28005u.jpg/240px-Aerial_views_of_the_Houston%2C_Texas%2C_28005u.jpg","state":"Texas","latitude":29.7604,"longitude":-95.3698}
|
||||
]
|
21
packages/client/src/components/landing-component/pin.tsx
Normal file
21
packages/client/src/components/landing-component/pin.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const ICON = `M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3
|
||||
c0,0,0.1,0.1,0.1,0.2c0.2,0.3,0.4,0.6,0.7,0.9c2.6,3.1,7.4,7.6,7.4,7.6s4.8-4.5,7.4-7.5c0.2-0.3,0.5-0.6,0.7-0.9
|
||||
C20.1,15.8,20.2,15.8,20.2,15.7z`;
|
||||
|
||||
const pinStyle = {
|
||||
cursor: 'pointer',
|
||||
fill: '#d00',
|
||||
stroke: 'none'
|
||||
};
|
||||
|
||||
function Pin({size = 20}) {
|
||||
return (
|
||||
<svg height={size} viewBox="0 0 24 24" style={pinStyle}>
|
||||
<path d={ICON} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Pin);
|
@@ -2,6 +2,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Chakra } from '../components/contexts/ChakraContext';
|
||||
import ComponentContext, { ComponentProvider } from '../components/contexts/ComponentContext.tsx';
|
||||
import { MobileProvider } from '../components/contexts/MobileContext';
|
||||
import { PageContextProvider } from '../renderer/usePageContext';
|
||||
import userOptionsStore from '../stores/UserOptionsStore';
|
||||
@@ -13,6 +14,7 @@ export { Layout };
|
||||
|
||||
const Layout = observer(({ pageContext, children }) => {
|
||||
const [activeTheme, setActiveTheme] = useState<string>('darknight');
|
||||
const [enabledComponent, setEnabledComponent] = useState('gpsmap');
|
||||
|
||||
useEffect(() => {
|
||||
if (userOptionsStore.theme !== activeTheme) {
|
||||
@@ -47,7 +49,9 @@ const Layout = observer(({ pageContext, children }) => {
|
||||
<PageContextProvider pageContext={pageContext}>
|
||||
<MobileProvider>
|
||||
<Chakra theme={getTheme(activeTheme)}>
|
||||
<LayoutComponent>{children}</LayoutComponent>
|
||||
<ComponentProvider>
|
||||
<LayoutComponent>{children}</LayoutComponent>
|
||||
</ComponentProvider>
|
||||
</Chakra>
|
||||
</MobileProvider>
|
||||
</PageContextProvider>
|
||||
|
@@ -42,6 +42,7 @@ const Navigation = observer(({ children, routeRegistry }) => {
|
||||
|
||||
return (
|
||||
<Grid templateColumns="1fr" templateRows="auto 1fr">
|
||||
{/*this is the menu button*/}
|
||||
<GridItem
|
||||
p={4}
|
||||
position="fixed"
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { Grid, GridItem, Stack } from '@chakra-ui/react';
|
||||
import { Box, Grid, GridItem, Stack } from '@chakra-ui/react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import Chat from '../../components/chat/Chat.tsx';
|
||||
import { useComponent } from '../../components/contexts/ComponentContext.tsx';
|
||||
import { LandingComponent } from '../../components/landing-component/LandingComponent.tsx';
|
||||
import ReactMap from '../../components/landing-component/Map.tsx';
|
||||
import clientChatStore from '../../stores/ClientChatStore';
|
||||
|
||||
// renders "/"
|
||||
@@ -16,13 +18,30 @@ export default function IndexPage() {
|
||||
// Fall back to default model
|
||||
}
|
||||
}, []);
|
||||
|
||||
const component = useComponent();
|
||||
|
||||
return (
|
||||
<Grid templateColumns="repeat(2, 1fr)" height="100%" width="100%" gap={0}>
|
||||
<GridItem>
|
||||
<LandingComponent />
|
||||
</GridItem>
|
||||
<GridItem p={2}>
|
||||
<Chat />
|
||||
<Box
|
||||
display={component.enabledComponent === 'ai' ? undefined : 'none'}
|
||||
width="100%"
|
||||
height="100%"
|
||||
overflowY="scroll"
|
||||
>
|
||||
<Chat />
|
||||
</Box>
|
||||
<Box
|
||||
display={component.enabledComponent === 'gpsmap' ? undefined : 'none'}
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
<ReactMap visible={component.enabledComponent === 'gpsmap'} />
|
||||
</Box>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
);
|
||||
|
@@ -20,10 +20,9 @@
|
||||
{
|
||||
"binding": "KV_STORAGE",
|
||||
// $ npx wrangler kv namespace create open-gsio
|
||||
// $ npx wrangler kv namespace create open-gsio
|
||||
"id": "placeholderId",
|
||||
"id": "",
|
||||
// $ npx wrangler kv namespace create open-gsio --preview
|
||||
"preview_id": "placeholderId"
|
||||
"preview_id": ""
|
||||
}
|
||||
],
|
||||
"migrations": [
|
||||
|
Reference in New Issue
Block a user