Skip to content

Commit

Permalink
Merge pull request #131 from yuiseki/floating-chat-ui
Browse files Browse the repository at this point in the history
γƒγƒ£γƒƒγƒˆιƒ¨εˆ†γ‚’ζ–°UIにする
  • Loading branch information
yuiseki authored Jan 6, 2024
2 parents df61a38 + 202b0a3 commit de0d50d
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 128 deletions.
52 changes: 35 additions & 17 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -193,30 +193,48 @@ main.tridentMain {
margin: auto;
}

.logsOuterWrap {
color: rgb(236, 236, 241);
background: rgba(15, 15, 15, 0.4);
backdrop-filter: blur(10px);
margin-bottom: 8px;
border-radius: 0.75rem;
border: 8px solid transparent;
display: flex;
flex-direction: column;
width: 400px;
max-width: 95vw;
min-height: 52px;
max-height: 300px;
overflow: auto;
padding: 0 !important;
}

.logsOuterWrap::-webkit-scrollbar {
width: 6px;
}

.logsOuterWrap::-webkit-scrollbar-track {
display: none;
}

.logsOuterWrap::-webkit-scrollbar-thumb {
border-radius: 0.75rem;
border: 10px solid rgba(0, 0, 0, 0.5);
}

.tridentMapWrap {
position: fixed;
top: 0;
left: 40%;
left: 0;
right: 0;
bottom: 0;
margin: 0;
width: 60%;
width: 100%;
height: 100%;
z-index: 1000;
}

.tridentMapSelectWrap select {
font-size: 1rem;
line-height: 1.4;
appearance: none;
border-radius: 12px;
min-height: 20px;
margin: 0px;
padding: 2px 8px;
cursor: pointer;
color: rgba(0, 0, 0, 0.8);
background-color: white;
font-family: sans-serif, emoji;
}

.tridentAgentArticle,
.tridentAgentReportListWrap {
height: 100vh;
Expand Down Expand Up @@ -573,7 +591,7 @@ main.tridentMain {
width: 100%;
top: 0;
left: 0;
height: 50dvh;
height: 100dvh;
}
.dialogueElementItem {
gap: 0px;
Expand Down
214 changes: 114 additions & 100 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,37 @@ import * as turf from "@turf/turf";
import { TridentMapsStyle } from "@/types/TridentMaps";
import { useLocalStorage } from "@/hooks/localStorage";
import { FloatingChatButton } from "@/components/FloatingActionButton";
import { MapStyleSelector } from "@/components/MapStyleSelector";

const greetings = `Hello! I'm TRIDENT, interactive Smart Maps assistant. Could you indicate me the areas and themes you want to see as the map?`;

export default function Home() {
// input ref and state
const textareaRef = useRef<HTMLTextAreaElement>(null);
const [inputText, setInputText] = useState("");

// dialogue ref and state
const dialogueRef = useRef<HTMLDivElement | null>(null);
const dialogueEndRef = useRef<HTMLDivElement | null>(null);
const [dialogueList, setDialogueList] = useState<DialogueElement[]>([]);
const [lazyInserting, setLazyInserting] = useState(false);
const [insertingText, setInsertingText] = useState(greetings);

const scrollToBottom = useCallback(async () => {
await sleep(50);
if (dialogueEndRef.current) {
dialogueEndRef.current.scrollIntoView({ behavior: "instant" });
}
}, []);

// floating chat button state
const [showingFloatingChat, setShowingFloatingChat] = useState(true);
const onChangeFloatingChatButton = useCallback((showing: boolean) => {
setShowingFloatingChat(showing);
if (showing) {
scrollToBottom();
}
}, [scrollToBottom]);

// maps ref and state
const mapRef = useRef<MapRef | null>(null);
const [geojsonWithStyleList, setGeojsonWithStyleList] = useState<
Expand All @@ -42,43 +65,71 @@ export default function Home() {
},
[setMapStyleJsonUrl]
);

// fit bounds to all geojson in the geojsonWithStyleList
useEffect(() => {
setTimeout(() => {
if (!mapRef || !mapRef.current) return;
if (geojsonWithStyleList.length === 0) return;

try {
console.log(geojsonWithStyleList);
// everything - all geojson in the geojsonWithStyleList
const everything: FeatureCollection = {
type: "FeatureCollection",
features: geojsonWithStyleList
.map((item) => item.geojson.features)
.flat(),
};

// bounding box of all everything
const [minLng, minLat, maxLng, maxLat] = turf.bbox(everything);

// padding of the map
let padding = {
top: 40,
left: 40,
right: 40,
bottom: 40,
};

// if floating chat is showing, add more padding
if (showingFloatingChat) {
const windowWidth = window.innerWidth;
// if the window is big like desktop, add little padding to left and bottom
// if the window is small like mobile, add more padding to bottom
if (windowWidth > 600) {
padding = {
top: 40,
left: 40,
right: 120,
bottom: 120,
};
} else {
padding = {
top: 40,
left: 40,
right: 40,
bottom: 400,
};
}
}

mapRef.current.fitBounds(
[
[minLng, minLat],
[maxLng, maxLat],
],
{ padding: 40, duration: 1000 }
{
padding: padding,
duration: 1000,
}
);
} catch (error) {
console.error(error);
}
}, 500);
}, [geojsonWithStyleList]);
}, [geojsonWithStyleList, showingFloatingChat]);

// dialogue ref and state
const dialogueRef = useRef<HTMLDivElement | null>(null);
const [dialogueList, setDialogueList] = useState<DialogueElement[]>([]);
const [lazyInserting, setLazyInserting] = useState(false);
const [insertingText, setInsertingText] = useState(greetings);
const scrollToBottom = useCallback(async () => {
await sleep(100);
if (dialogueRef.current) {
dialogueRef.current.scrollTop = dialogueRef.current.scrollHeight;
}
}, []);
const insertNewDialogue = useCallback(
(newDialogueElement: DialogueElement, lazy?: boolean) => {
if (!lazy) {
Expand Down Expand Up @@ -319,10 +370,6 @@ export default function Home() {
},
false
);
} else {
if (textareaRef.current) {
textareaRef.current.focus();
}
}
}, [mounted, insertNewDialogue]);
if (!mounted) return null;
Expand All @@ -331,88 +378,11 @@ export default function Home() {
<>
<title>{pageTitle}</title>
<main className="tridentMain">
<div className="tridentBackgroundWrap">
<div className="tridentBackgroundFlag"></div>
<div className="tridentBackgroundOverlay"></div>
</div>
<div className="tridentDialogueOuterWrap" ref={dialogueRef}>
<div className="tridentMapTitle">{mapTitle}</div>
<div className="tridentDialogueInnerWrap">
{dialogueList.map((dialogueElement, dialogueIndex) => {
return (
<div key={dialogueIndex}>
<DialogueElementView
dialogueElement={dialogueElement}
dialogueIndex={dialogueIndex}
isResponding={
(responding || lazyInserting || mapping) &&
dialogueIndex === dialogueList.length - 1
}
/>
</div>
);
})}
</div>
</div>
<div className="tridentInputOuterWrap">
<div className="tridentInputInnerWrap">
<TextInput
textareaRef={textareaRef}
disabled={responding || lazyInserting || mapping}
placeholder={
responding || lazyInserting || mapping
? "..."
: "Show embassies in Lebanon."
}
inputText={inputText}
setInputText={setInputText}
onSubmit={onSubmit}
/>
</div>
<div
style={{
fontSize: "0.8rem",
color: "white",
width: "100%",
textAlign: "center",
opacity: 0.8,
}}
>
TRIDENT may produce inaccurate information.
</div>
</div>
<div className="tridentMapWrap">
<div className="tridentMapSelectWrap">
<select
style={{
position: "absolute",
top: 10,
left: 10,
zIndex: 10000,
maxWidth: "250px",
textOverflow: "ellipsis",
}}
value={mapStyleJsonUrl}
onChange={onSelectMapStyleJsonUrl}
>
<option value={"/map_styles/fiord-color-gl-style/style.json"}>
πŸ—Ί OSM Fiord color (vector)
</option>
<option
value={
"https://tile.openstreetmap.jp/styles/osm-bright/style.json"
}
>
πŸ—Ί OSM JP bright (vector)
</option>
<option value={"/map_styles/osm-hot/style.json"}>
πŸ—Ί OSM HOT (raster)
</option>
<option value={"/map_styles/arcgis-world-imagery/style.json"}>
πŸ›° ArcGIS World Imagery (raster)
</option>
</select>
</div>
<MapStyleSelector
mapStyleJsonUrl={mapStyleJsonUrl}
onSelectMapStyleJsonUrl={onSelectMapStyleJsonUrl}
/>
<MapProvider>
<BaseMap
id="mainMap"
Expand All @@ -434,6 +404,50 @@ export default function Home() {
})}
</BaseMap>
</MapProvider>
<FloatingChatButton onChange={onChangeFloatingChatButton}>
<div className="logsOuterWrap" ref={dialogueRef}>
<div className="tridentMapTitle">
{mapTitle ? mapTitle : "Untitled Map"}
</div>
{dialogueList.map((dialogueElement, dialogueIndex) => {
return (
<div key={dialogueIndex}>
<DialogueElementView
dialogueElement={dialogueElement}
dialogueIndex={dialogueIndex}
isResponding={
(responding || lazyInserting || mapping) &&
dialogueIndex === dialogueList.length - 1
}
/>
</div>
);
})}
<div style={{ height: '1px' }} ref={dialogueEndRef} />
</div>
<TextInput
disabled={responding || lazyInserting || mapping}
placeholder={
responding || lazyInserting || mapping
? "..."
: "Show embassies in Lebanon."
}
inputText={inputText}
setInputText={setInputText}
onSubmit={onSubmit}
/>
<div
style={{
fontSize: "0.8rem",
color: "white",
width: "100%",
textAlign: "center",
opacity: 0.8,
}}
>
TRIDENT may produce inaccurate information.
</div>
</FloatingChatButton>
</div>
</main>
</>
Expand Down
12 changes: 10 additions & 2 deletions src/components/FloatingActionButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/* eslint-disable @next/next/no-img-element */
"use client";

import { useState } from "react";
import styles from "./styles.module.scss";

export const FloatingChatButton: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
const [showing, setShowing] = useState(false);
onChange?: (showing: boolean) => void;
}> = ({ children, onChange }) => {
const [showing, setShowing] = useState(true);
return (
<>
{!showing && (
Expand All @@ -16,6 +18,9 @@ export const FloatingChatButton: React.FC<{
className={styles.button}
onClick={() => {
setShowing(true);
if (onChange) {
onChange(true);
}
}}
>
<img
Expand All @@ -35,6 +40,9 @@ export const FloatingChatButton: React.FC<{
className={styles.closeButton}
onClick={() => {
setShowing(false);
if (onChange) {
onChange(false);
}
}}
>
βœ–
Expand Down
Loading

1 comment on commit de0d50d

@vercel
Copy link

@vercel vercel bot commented on de0d50d Jan 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.