Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
5011eb5d
Commit
5011eb5d
authored
Oct 14, 2025
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: enhance LocationSelector with improved geolocation handling
parent
12c4aeec
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
57 additions
and
23 deletions
+57
-23
LocationSelector.tsx
...c/components/MemoEditor/ActionButton/LocationSelector.tsx
+57
-23
No files found.
web/src/components/MemoEditor/ActionButton/LocationSelector.tsx
View file @
5011eb5d
...
@@ -5,7 +5,9 @@ import toast from "react-hot-toast";
...
@@ -5,7 +5,9 @@ import toast from "react-hot-toast";
import
LeafletMap
from
"@/components/LeafletMap"
;
import
LeafletMap
from
"@/components/LeafletMap"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Label
}
from
"@/components/ui/label"
;
import
{
Popover
,
PopoverContent
,
PopoverTrigger
}
from
"@/components/ui/popover"
;
import
{
Popover
,
PopoverContent
,
PopoverTrigger
}
from
"@/components/ui/popover"
;
import
{
Textarea
}
from
"@/components/ui/textarea"
;
import
{
Tooltip
,
TooltipContent
,
TooltipProvider
,
TooltipTrigger
}
from
"@/components/ui/tooltip"
;
import
{
Tooltip
,
TooltipContent
,
TooltipProvider
,
TooltipTrigger
}
from
"@/components/ui/tooltip"
;
import
{
Location
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
Location
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
...
@@ -47,7 +49,7 @@ const LocationSelector = (props: Props) => {
...
@@ -47,7 +49,7 @@ const LocationSelector = (props: Props) => {
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
popoverOpen
&&
!
props
.
location
)
{
if
(
popoverOpen
&&
!
props
.
location
)
{
const
handleError
=
(
error
:
any
,
errorMessage
:
string
)
=>
{
const
handleError
=
(
error
:
any
,
errorMessage
:
string
)
=>
{
setState
(
{
...
state
,
initialized
:
true
}
);
setState
(
(
prev
)
=>
({
...
prev
,
initialized
:
true
})
);
toast
.
error
(
errorMessage
);
toast
.
error
(
errorMessage
);
console
.
error
(
error
);
console
.
error
(
error
);
};
};
...
@@ -57,7 +59,13 @@ const LocationSelector = (props: Props) => {
...
@@ -57,7 +59,13 @@ const LocationSelector = (props: Props) => {
(
position
)
=>
{
(
position
)
=>
{
const
lat
=
position
.
coords
.
latitude
;
const
lat
=
position
.
coords
.
latitude
;
const
lng
=
position
.
coords
.
longitude
;
const
lng
=
position
.
coords
.
longitude
;
setState
({
...
state
,
position
:
new
LatLng
(
lat
,
lng
),
initialized
:
true
});
setState
((
prev
)
=>
({
...
prev
,
position
:
new
LatLng
(
lat
,
lng
),
latInput
:
String
(
lat
),
lngInput
:
String
(
lng
),
initialized
:
true
,
}));
},
},
(
error
)
=>
{
(
error
)
=>
{
handleError
(
error
,
"Failed to get current position"
);
handleError
(
error
,
"Failed to get current position"
);
...
@@ -67,7 +75,7 @@ const LocationSelector = (props: Props) => {
...
@@ -67,7 +75,7 @@ const LocationSelector = (props: Props) => {
handleError
(
"Geolocation is not supported by this browser."
,
"Geolocation is not supported by this browser."
);
handleError
(
"Geolocation is not supported by this browser."
,
"Geolocation is not supported by this browser."
);
}
}
}
}
},
[
popoverOpen
]);
},
[
popoverOpen
,
props
.
location
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
state
.
position
)
{
if
(
!
state
.
position
)
{
...
@@ -100,7 +108,8 @@ const LocationSelector = (props: Props) => {
...
@@ -100,7 +108,8 @@ const LocationSelector = (props: Props) => {
useEffect
(()
=>
{
useEffect
(()
=>
{
const
lat
=
parseFloat
(
state
.
latInput
);
const
lat
=
parseFloat
(
state
.
latInput
);
const
lng
=
parseFloat
(
state
.
lngInput
);
const
lng
=
parseFloat
(
state
.
lngInput
);
if
(
Number
.
isFinite
(
lat
)
&&
Number
.
isFinite
(
lng
))
{
// Validate coordinate ranges: lat must be -90 to 90, lng must be -180 to 180
if
(
Number
.
isFinite
(
lat
)
&&
Number
.
isFinite
(
lng
)
&&
lat
>=
-
90
&&
lat
<=
90
&&
lng
>=
-
180
&&
lng
<=
180
)
{
if
(
!
state
.
position
||
state
.
position
.
lat
!==
lat
||
state
.
position
.
lng
!==
lng
)
{
if
(
!
state
.
position
||
state
.
position
.
lat
!==
lat
||
state
.
position
.
lng
!==
lng
)
{
setState
((
prev
)
=>
({
...
prev
,
position
:
new
LatLng
(
lat
,
lng
)
}));
setState
((
prev
)
=>
({
...
prev
,
position
:
new
LatLng
(
lat
,
lng
)
}));
}
}
...
@@ -145,41 +154,66 @@ const LocationSelector = (props: Props) => {
...
@@ -145,41 +154,66 @@ const LocationSelector = (props: Props) => {
)
}
)
}
</
Tooltip
>
</
Tooltip
>
</
TooltipProvider
>
</
TooltipProvider
>
<
PopoverContent
align=
"center"
>
<
PopoverContent
align=
"center"
className=
"w-[min(24rem,calc(100vw-2rem))] p-0"
>
<
div
className=
"min-w-80 sm:w-lg p-1 flex flex-col justify-start items-start"
>
<
div
className=
"flex flex-col gap-4 p-4"
>
<
div
className=
"w-full overflow-hidden rounded-lg border bg-muted/30 shadow-xs"
>
<
LeafletMap
key=
{
JSON
.
stringify
(
state
.
initialized
)
}
latlng=
{
state
.
position
}
onChange=
{
onPositionChanged
}
/>
<
LeafletMap
key=
{
JSON
.
stringify
(
state
.
initialized
)
}
latlng=
{
state
.
position
}
onChange=
{
onPositionChanged
}
/>
<
div
className=
"mt-2 w-full flex flex-row justify-between items-center gap-2"
>
</
div
>
<
div
className=
"flex flex-row items-center justify-start gap-2 w-full"
>
<
div
className=
"w-full space-y-3"
>
<
div
id=
"lat-long-display"
className=
"flex flex-row items-center gap-2"
>
<
div
className=
"grid grid-cols-2 gap-3"
>
<
div
className=
"grid gap-1"
>
<
Label
htmlFor=
"memo-location-lat"
className=
"text-xs font-medium uppercase tracking-wide text-muted-foreground"
>
Lat
</
Label
>
<
Input
<
Input
id=
"memo-location-lat"
placeholder=
"Lat"
placeholder=
"Lat"
type=
"number"
type=
"number"
step=
"any"
step=
"any"
min=
"-90"
max=
"90"
value=
{
state
.
latInput
}
value=
{
state
.
latInput
}
onChange=
{
(
e
)
=>
setState
((
prev
)
=>
({
...
prev
,
latInput
:
e
.
target
.
value
}))
}
onChange=
{
(
e
)
=>
setState
((
prev
)
=>
({
...
prev
,
latInput
:
e
.
target
.
value
}))
}
className=
"
w-28
"
className=
"
h-9
"
/>
/>
</
div
>
<
div
className=
"grid gap-1"
>
<
Label
htmlFor=
"memo-location-lng"
className=
"text-xs font-medium uppercase tracking-wide text-muted-foreground"
>
Lng
</
Label
>
<
Input
<
Input
id=
"memo-location-lng"
placeholder=
"Lng"
placeholder=
"Lng"
type=
"number"
type=
"number"
step=
"any"
step=
"any"
min=
"-180"
max=
"180"
value=
{
state
.
lngInput
}
value=
{
state
.
lngInput
}
onChange=
{
(
e
)
=>
setState
((
prev
)
=>
({
...
prev
,
lngInput
:
e
.
target
.
value
}))
}
onChange=
{
(
e
)
=>
setState
((
prev
)
=>
({
...
prev
,
lngInput
:
e
.
target
.
value
}))
}
className=
"
w-28
"
className=
"
h-9
"
/>
/>
<
div
className=
"relative flex-1"
>
</
div
>
<
Input
</
div
>
<
div
className=
"grid gap-1"
>
<
Label
htmlFor=
"memo-location-placeholder"
className=
"text-xs font-medium uppercase tracking-wide text-muted-foreground"
>
{
t
(
"tooltip.select-location"
)
}
</
Label
>
<
Textarea
id=
"memo-location-placeholder"
placeholder=
"Choose a position first."
placeholder=
"Choose a position first."
value=
{
state
.
placeholder
}
value=
{
state
.
placeholder
}
disabled=
{
!
state
.
position
}
disabled=
{
!
state
.
position
}
onChange=
{
(
e
)
=>
setState
((
prev
)
=>
({
...
prev
,
placeholder
:
e
.
target
.
value
}))
}
onChange=
{
(
e
)
=>
setState
((
prev
)
=>
({
...
prev
,
placeholder
:
e
.
target
.
value
}))
}
className=
"min-h-16"
/>
/>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
"flex items-center justify-end gap-2"
>
<
Button
variant=
"ghost"
size=
"sm"
onClick=
{
()
=>
setPopoverOpen
(
false
)
}
>
{
t
(
"common.cancel"
)
}
</
Button
>
<
Button
<
Button
className=
"shrink-0"
size=
"sm"
color=
"primary"
onClick=
{
()
=>
{
onClick=
{
()
=>
{
props
.
onChange
(
props
.
onChange
(
Location
.
fromPartial
({
Location
.
fromPartial
({
...
@@ -190,7 +224,7 @@ const LocationSelector = (props: Props) => {
...
@@ -190,7 +224,7 @@ const LocationSelector = (props: Props) => {
);
);
setPopoverOpen
(
false
);
setPopoverOpen
(
false
);
}
}
}
}
disabled=
{
!
state
.
position
||
state
.
placeholder
.
length
===
0
}
disabled=
{
!
state
.
position
||
state
.
placeholder
.
trim
().
length
===
0
}
>
>
{
t
(
"common.confirm"
)
}
{
t
(
"common.confirm"
)
}
</
Button
>
</
Button
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment