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-map-gl": "^8.0.4",
|
||||||
"react-streaming": "^0.4.2",
|
"react-streaming": "^0.4.2",
|
||||||
"react-textarea-autosize": "^8.5.5",
|
"react-textarea-autosize": "^8.5.5",
|
||||||
"react-use-pwa-install": "^1.0.3",
|
|
||||||
"shiki": "^1.24.0",
|
"shiki": "^1.24.0",
|
||||||
"tslog": "^4.9.3",
|
"tslog": "^4.9.3",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
@@ -1646,8 +1645,6 @@
|
|||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"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=="],
|
"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=="],
|
"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-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=="],
|
"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=="],
|
"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`;
|
const currentTime = `${now.getHours()}:${formattedMinutes} ${now.getSeconds()}s`;
|
||||||
|
|
||||||
return `# Assistant Knowledge
|
return `# Assistant Knowledge
|
||||||
|
## Assistant Name
|
||||||
|
### yachtpit-ai
|
||||||
## Current Context
|
## Current Context
|
||||||
### Date: ${currentDate} ${currentTime}
|
### Date: ${currentDate} ${currentTime}
|
||||||
### Web Host: open-gsio.seemueller.workers.dev
|
|
||||||
${maxTokens ? `### Max Response Length: ${maxTokens} tokens (maximum)` : ''}
|
${maxTokens ? `### Max Response Length: ${maxTokens} tokens (maximum)` : ''}
|
||||||
### Lexicographical Format: Markdown
|
### Lexicographical Format: Markdown
|
||||||
### User Location: ${userLocation || 'Unknown'}
|
### User Location: ${userLocation || 'Unknown'}
|
||||||
|
@@ -58,7 +58,6 @@
|
|||||||
"react-map-gl": "^8.0.4",
|
"react-map-gl": "^8.0.4",
|
||||||
"react-streaming": "^0.4.2",
|
"react-streaming": "^0.4.2",
|
||||||
"react-textarea-autosize": "^8.5.5",
|
"react-textarea-autosize": "^8.5.5",
|
||||||
"react-use-pwa-install": "^1.0.3",
|
|
||||||
"shiki": "^1.24.0",
|
"shiki": "^1.24.0",
|
||||||
"tslog": "^4.9.3",
|
"tslog": "^4.9.3",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
import { IconButton } from '@chakra-ui/react';
|
import { IconButton } from '@chakra-ui/react';
|
||||||
import { HardDriveDownload } from 'lucide-react';
|
import { HardDriveDownload } from 'lucide-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { usePWAInstall } from 'react-use-pwa-install';
|
|
||||||
|
|
||||||
import { toolbarButtonZIndex } from './toolbar/Toolbar.tsx';
|
import { toolbarButtonZIndex } from './toolbar/Toolbar.tsx';
|
||||||
|
|
||||||
function InstallButton() {
|
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 (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Install App"
|
aria-label="Install App"
|
||||||
|
@@ -28,7 +28,7 @@ const Chat = observer(({ height, width }) => {
|
|||||||
<GridItem
|
<GridItem
|
||||||
overflow="auto"
|
overflow="auto"
|
||||||
width="100%"
|
width="100%"
|
||||||
maxH="100%"
|
maxH="100vh"
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
// If there are attachments, use "100px". Otherwise, use "128px" on Android, "73px" elsewhere.
|
// If there are attachments, use "100px". Otherwise, use "128px" on Android, "73px" elsewhere.
|
||||||
pb={isAndroid ? '128px' : '73px'}
|
pb={isAndroid ? '128px' : '73px'}
|
||||||
|
@@ -22,7 +22,7 @@ const ChatInput = observer(() => {
|
|||||||
const [shouldFollow, setShouldFollow] = useState<boolean>(userOptionsStore.followModeEnabled);
|
const [shouldFollow, setShouldFollow] = useState<boolean>(userOptionsStore.followModeEnabled);
|
||||||
const [couldFollow, setCouldFollow] = useState<boolean>(chatStore.isLoading);
|
const [couldFollow, setCouldFollow] = useState<boolean>(chatStore.isLoading);
|
||||||
|
|
||||||
const [inputWidth, setInputWidth] = useState<string>('50%');
|
const [inputWidth, setInputWidth] = useState<string>('40%');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShouldFollow(chatStore.isLoading && userOptionsStore.followModeEnabled);
|
setShouldFollow(chatStore.isLoading && userOptionsStore.followModeEnabled);
|
||||||
@@ -64,10 +64,10 @@ const ChatInput = observer(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const inputMaxWidth = useBreakpointValue(
|
const inputMaxWidth = useBreakpointValue(
|
||||||
{ base: '50rem', lg: '50rem', md: '80%', sm: '100vw' },
|
{ base: '30rem', lg: '50rem', md: '80%', sm: '100vw' },
|
||||||
{ ssr: true },
|
{ ssr: true },
|
||||||
);
|
);
|
||||||
const inputMinWidth = useBreakpointValue({ lg: '40rem' }, { ssr: true });
|
const inputMinWidth = useBreakpointValue({ lg: '40rem', md: '30rem' }, { ssr: true });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setInputWidth('100%');
|
setInputWidth('100%');
|
||||||
@@ -75,9 +75,7 @@ const ChatInput = observer(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
width={inputWidth}
|
width={inputMinWidth}
|
||||||
maxW={inputMaxWidth}
|
|
||||||
minWidth={inputMinWidth}
|
|
||||||
mx="auto"
|
mx="auto"
|
||||||
p={2}
|
p={2}
|
||||||
pl={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 { observer } from 'mobx-react-lite';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import AutoResize from 'react-textarea-autosize';
|
import AutoResize from 'react-textarea-autosize';
|
||||||
@@ -19,7 +19,7 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value.length > 10) {
|
if (value.length > 10) {
|
||||||
setHeightConstraint();
|
setHeightConstraint(parseInt(value));
|
||||||
}
|
}
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
|
|||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
value={value}
|
value={value}
|
||||||
height={heightConstraint}
|
height={heightConstraint}
|
||||||
|
maxH={heightConstraint}
|
||||||
autoFocus
|
autoFocus
|
||||||
onChange={e => onChange(e.target.value)}
|
onChange={e => onChange(e.target.value)}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
@@ -48,8 +49,14 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
|
|||||||
color="text.primary"
|
color="text.primary"
|
||||||
borderRadius="20px"
|
borderRadius="20px"
|
||||||
border="none"
|
border="none"
|
||||||
placeholder="Free my mind..."
|
placeholder="To Gilligan's island!"
|
||||||
_placeholder={{ color: 'gray.400' }}
|
_placeholder={{
|
||||||
|
color: 'gray.400',
|
||||||
|
textWrap: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
_focus={{
|
_focus={{
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
}}
|
}}
|
||||||
|
@@ -9,7 +9,7 @@ export function formatConversationMarkdown(messages: Instance<typeof IMessage>[]
|
|||||||
if (message.role === 'user') {
|
if (message.role === 'user') {
|
||||||
return `**You**: ${message.content}`;
|
return `**You**: ${message.content}`;
|
||||||
} else if (message.role === 'assistant') {
|
} else if (message.role === 'assistant') {
|
||||||
return `**Geoff's AI**: ${message.content}`;
|
return `**yachtpit-ai**: ${message.content}`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
})
|
})
|
||||||
|
@@ -51,7 +51,7 @@ const MessageBubble = observer(({ msg, scrollRef }) => {
|
|||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const isUser = msg.role === 'user';
|
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 isLoading = !msg.content || !(msg.content.trim().length > 0);
|
||||||
const messageRef = useRef();
|
const messageRef = useRef();
|
||||||
|
|
||||||
|
@@ -104,7 +104,7 @@ describe('MessageBubble', () => {
|
|||||||
it('should render assistant message correctly', () => {
|
it('should render assistant message correctly', () => {
|
||||||
render(<MessageBubble msg={mockAssistantMessage} scrollRef={mockScrollRef} />);
|
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');
|
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';
|
import React, { memo, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
export interface BevySceneProps {
|
export interface BevySceneProps {
|
||||||
@@ -14,6 +14,8 @@ const BevySceneInner: React.FC<BevySceneProps> = ({
|
|||||||
glow = false,
|
glow = false,
|
||||||
visible,
|
visible,
|
||||||
}) => {
|
}) => {
|
||||||
|
const maxWidth = useBreakpointValue({ base: 640, md: 720 }, { ssr: true });
|
||||||
|
|
||||||
/* initialise once */
|
/* initialise once */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let dispose: (() => void) | void;
|
let dispose: (() => void) | void;
|
||||||
@@ -31,7 +33,8 @@ const BevySceneInner: React.FC<BevySceneProps> = ({
|
|||||||
() => ({
|
() => ({
|
||||||
position: 'absolute' as const,
|
position: 'absolute' as const,
|
||||||
inset: 0,
|
inset: 0,
|
||||||
zIndex: 0,
|
zIndex: 1,
|
||||||
|
maxWidth: maxWidth,
|
||||||
opacity: visible ? Math.min(Math.max(intensity, 0), 1) : 0,
|
opacity: visible ? Math.min(Math.max(intensity, 0), 1) : 0,
|
||||||
filter: glow ? 'blur(1px)' : 'none',
|
filter: glow ? 'blur(1px)' : 'none',
|
||||||
transition: `opacity ${speed}s ease-in-out`,
|
transition: `opacity ${speed}s ease-in-out`,
|
||||||
@@ -42,7 +45,12 @@ const BevySceneInner: React.FC<BevySceneProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box as="div" sx={wrapperStyles}>
|
<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>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,20 +1,30 @@
|
|||||||
import { Box } from '@chakra-ui/react';
|
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 { BevyScene } from './BevyScene.tsx';
|
||||||
import Map from './Map.tsx';
|
|
||||||
import Tweakbox from './Tweakbox.tsx';
|
import Tweakbox from './Tweakbox.tsx';
|
||||||
|
|
||||||
export const LandingComponent: React.FC = () => {
|
export const LandingComponent: React.FC = () => {
|
||||||
const [speed, setSpeed] = useState(0.2);
|
const [speed, setSpeed] = useState(0.2);
|
||||||
const [intensity, setIntensity] = useState(0.5);
|
const [intensity, setIntensity] = useState(0.99);
|
||||||
const [particles, setParticles] = useState(false);
|
|
||||||
const [glow, setGlow] = useState(false);
|
const [glow, setGlow] = useState(false);
|
||||||
const [matrixRain, setMatrixRain] = useState(false);
|
|
||||||
const [bevyScene, setBevyScene] = useState(true);
|
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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -36,7 +46,7 @@ export const LandingComponent: React.FC = () => {
|
|||||||
<Tweakbox
|
<Tweakbox
|
||||||
sliders={{
|
sliders={{
|
||||||
intensity: {
|
intensity: {
|
||||||
value: !particles ? intensity : 0.99,
|
value: intensity,
|
||||||
onChange: setIntensity,
|
onChange: setIntensity,
|
||||||
label: 'Brightness',
|
label: 'Brightness',
|
||||||
min: 0.01,
|
min: 0.01,
|
||||||
@@ -49,11 +59,6 @@ export const LandingComponent: React.FC = () => {
|
|||||||
bevyScene: {
|
bevyScene: {
|
||||||
value: bevyScene,
|
value: bevyScene,
|
||||||
onChange(enabled) {
|
onChange(enabled) {
|
||||||
if (enabled) {
|
|
||||||
setParticles(!enabled);
|
|
||||||
setMatrixRain(!enabled);
|
|
||||||
setMapActive(!enabled);
|
|
||||||
}
|
|
||||||
setBevyScene(enabled);
|
setBevyScene(enabled);
|
||||||
},
|
},
|
||||||
label: 'Instruments',
|
label: 'Instruments',
|
||||||
@@ -62,19 +67,32 @@ export const LandingComponent: React.FC = () => {
|
|||||||
value: mapActive,
|
value: mapActive,
|
||||||
onChange(enabled) {
|
onChange(enabled) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
setParticles(!enabled);
|
setEnabledComponent('gpsmap');
|
||||||
setMatrixRain(!enabled);
|
setAiActive(false);
|
||||||
setBevyScene(!enabled);
|
} else {
|
||||||
|
setEnabledComponent('');
|
||||||
}
|
}
|
||||||
setMapActive(enabled);
|
setMapActive(enabled);
|
||||||
},
|
},
|
||||||
label: 'Map',
|
label: 'Map',
|
||||||
},
|
},
|
||||||
|
AI: {
|
||||||
|
value: aiActive,
|
||||||
|
onChange(enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
setEnabledComponent('ai');
|
||||||
|
setMapActive(false);
|
||||||
|
} else {
|
||||||
|
setEnabledComponent('');
|
||||||
|
}
|
||||||
|
setAiActive(enabled);
|
||||||
|
},
|
||||||
|
label: 'AI',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<BevyScene speed={speed} intensity={intensity} glow={glow} visible={bevyScene} />
|
<BevyScene speed={speed} intensity={intensity} glow={glow} visible={bevyScene} />
|
||||||
{mapActive && map}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -3,6 +3,8 @@ import 'mapbox-gl/dist/mapbox-gl.css';
|
|||||||
import { Box, HStack, Button, Input, Center } from '@chakra-ui/react';
|
import { Box, HStack, Button, Input, Center } from '@chakra-ui/react';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
|
||||||
|
import MapNext from './MapNext.tsx';
|
||||||
|
|
||||||
// Types for bevy_flurx_ipc communication
|
// Types for bevy_flurx_ipc communication
|
||||||
interface GpsPosition {
|
interface GpsPosition {
|
||||||
latitude: number;
|
latitude: number;
|
||||||
@@ -33,64 +35,27 @@ const key =
|
|||||||
'cGsuZXlKMUlqb2laMlZ2Wm1aelpXVWlMQ0poSWpvaVkycDFOalo0YkdWNk1EUTRjRE41YjJnNFp6VjNNelp6YXlKOS56LUtzS1l0X3VGUGdCSDYwQUFBNFNn';
|
'cGsuZXlKMUlqb2laMlZ2Wm1aelpXVWlMQ0poSWpvaVkycDFOalo0YkdWNk1EUTRjRE41YjJnNFp6VjNNelp6YXlKOS56LUtzS1l0X3VGUGdCSDYwQUFBNFNn';
|
||||||
|
|
||||||
function Map(props: { visible: boolean }) {
|
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 (
|
return (
|
||||||
<Box
|
/* Full-screen wrapper — fills the viewport and becomes the positioning context */
|
||||||
p={4}
|
<Box w="100%" h="100vh" position="relative" overflow="hidden">
|
||||||
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>
|
|
||||||
{/* Button bar — absolutely positioned inside the wrapper */}
|
{/* 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}>
|
<MapNext mapboxPublicKey={atob(key)} />
|
||||||
Navigation
|
{/*<Map*/}
|
||||||
</Button>
|
{/* mapboxAccessToken={atob(key)}*/}
|
||||||
<Button size="sm" variant="solid" onClick={handleSearchClick}>
|
{/* initialViewState={mapView}*/}
|
||||||
Search
|
{/* onMove={handleMapViewChange}*/}
|
||||||
</Button>
|
{/* mapStyle="mapbox://styles/mapbox/dark-v11"*/}
|
||||||
</HStack>
|
{/* 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>
|
</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 React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { Chakra } from '../components/contexts/ChakraContext';
|
import { Chakra } from '../components/contexts/ChakraContext';
|
||||||
|
import ComponentContext, { ComponentProvider } from '../components/contexts/ComponentContext.tsx';
|
||||||
import { MobileProvider } from '../components/contexts/MobileContext';
|
import { MobileProvider } from '../components/contexts/MobileContext';
|
||||||
import { PageContextProvider } from '../renderer/usePageContext';
|
import { PageContextProvider } from '../renderer/usePageContext';
|
||||||
import userOptionsStore from '../stores/UserOptionsStore';
|
import userOptionsStore from '../stores/UserOptionsStore';
|
||||||
@@ -13,6 +14,7 @@ export { Layout };
|
|||||||
|
|
||||||
const Layout = observer(({ pageContext, children }) => {
|
const Layout = observer(({ pageContext, children }) => {
|
||||||
const [activeTheme, setActiveTheme] = useState<string>('darknight');
|
const [activeTheme, setActiveTheme] = useState<string>('darknight');
|
||||||
|
const [enabledComponent, setEnabledComponent] = useState('gpsmap');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userOptionsStore.theme !== activeTheme) {
|
if (userOptionsStore.theme !== activeTheme) {
|
||||||
@@ -47,7 +49,9 @@ const Layout = observer(({ pageContext, children }) => {
|
|||||||
<PageContextProvider pageContext={pageContext}>
|
<PageContextProvider pageContext={pageContext}>
|
||||||
<MobileProvider>
|
<MobileProvider>
|
||||||
<Chakra theme={getTheme(activeTheme)}>
|
<Chakra theme={getTheme(activeTheme)}>
|
||||||
|
<ComponentProvider>
|
||||||
<LayoutComponent>{children}</LayoutComponent>
|
<LayoutComponent>{children}</LayoutComponent>
|
||||||
|
</ComponentProvider>
|
||||||
</Chakra>
|
</Chakra>
|
||||||
</MobileProvider>
|
</MobileProvider>
|
||||||
</PageContextProvider>
|
</PageContextProvider>
|
||||||
|
@@ -42,6 +42,7 @@ const Navigation = observer(({ children, routeRegistry }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid templateColumns="1fr" templateRows="auto 1fr">
|
<Grid templateColumns="1fr" templateRows="auto 1fr">
|
||||||
|
{/*this is the menu button*/}
|
||||||
<GridItem
|
<GridItem
|
||||||
p={4}
|
p={4}
|
||||||
position="fixed"
|
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 React, { useEffect } from 'react';
|
||||||
|
|
||||||
import Chat from '../../components/chat/Chat.tsx';
|
import Chat from '../../components/chat/Chat.tsx';
|
||||||
|
import { useComponent } from '../../components/contexts/ComponentContext.tsx';
|
||||||
import { LandingComponent } from '../../components/landing-component/LandingComponent.tsx';
|
import { LandingComponent } from '../../components/landing-component/LandingComponent.tsx';
|
||||||
|
import ReactMap from '../../components/landing-component/Map.tsx';
|
||||||
import clientChatStore from '../../stores/ClientChatStore';
|
import clientChatStore from '../../stores/ClientChatStore';
|
||||||
|
|
||||||
// renders "/"
|
// renders "/"
|
||||||
@@ -16,13 +18,30 @@ export default function IndexPage() {
|
|||||||
// Fall back to default model
|
// Fall back to default model
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const component = useComponent();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid templateColumns="repeat(2, 1fr)" height="100%" width="100%" gap={0}>
|
<Grid templateColumns="repeat(2, 1fr)" height="100%" width="100%" gap={0}>
|
||||||
<GridItem>
|
<GridItem>
|
||||||
<LandingComponent />
|
<LandingComponent />
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem p={2}>
|
<GridItem p={2}>
|
||||||
|
<Box
|
||||||
|
display={component.enabledComponent === 'ai' ? undefined : 'none'}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
overflowY="scroll"
|
||||||
|
>
|
||||||
<Chat />
|
<Chat />
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
display={component.enabledComponent === 'gpsmap' ? undefined : 'none'}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
>
|
||||||
|
<ReactMap visible={component.enabledComponent === 'gpsmap'} />
|
||||||
|
</Box>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
@@ -20,10 +20,9 @@
|
|||||||
{
|
{
|
||||||
"binding": "KV_STORAGE",
|
"binding": "KV_STORAGE",
|
||||||
// $ npx wrangler kv namespace create open-gsio
|
// $ npx wrangler kv namespace create open-gsio
|
||||||
// $ npx wrangler kv namespace create open-gsio
|
"id": "",
|
||||||
"id": "placeholderId",
|
|
||||||
// $ npx wrangler kv namespace create open-gsio --preview
|
// $ npx wrangler kv namespace create open-gsio --preview
|
||||||
"preview_id": "placeholderId"
|
"preview_id": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"migrations": [
|
"migrations": [
|
||||||
|
Reference in New Issue
Block a user