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
b144faf4
Commit
b144faf4
authored
Sep 25, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add location selector
parent
63989ab3
Changes
12
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
651 additions
and
375 deletions
+651
-375
memo_service.proto
proto/api/v1/memo_service.proto
+2
-0
memo_service.pb.go
proto/gen/api/v1/memo_service.pb.go
+386
-372
apidocs.swagger.yaml
proto/gen/apidocs.swagger.yaml
+2
-0
memo_service.go
server/router/api/v1/memo_service.go
+3
-0
package.json
web/package.json
+3
-0
pnpm-lock.yaml
web/pnpm-lock.yaml
+48
-0
LeafletMap.tsx
web/src/components/LeafletMap.tsx
+46
-0
AddMemoRelationPopover.tsx
...onents/MemoEditor/ActionButton/AddMemoRelationPopover.tsx
+1
-1
LocationSelector.tsx
...c/components/MemoEditor/ActionButton/LocationSelector.tsx
+116
-0
index.tsx
web/src/components/MemoEditor/index.tsx
+34
-1
MemoView.tsx
web/src/components/MemoView.tsx
+9
-1
main.tsx
web/src/main.tsx
+1
-0
No files found.
proto/api/v1/memo_service.proto
View file @
b144faf4
...
@@ -215,6 +215,8 @@ message CreateMemoRequest {
...
@@ -215,6 +215,8 @@ message CreateMemoRequest {
repeated
Resource
resources
=
3
;
repeated
Resource
resources
=
3
;
repeated
MemoRelation
relations
=
4
;
repeated
MemoRelation
relations
=
4
;
optional
Location
location
=
5
;
}
}
message
ListMemosRequest
{
message
ListMemosRequest
{
...
...
proto/gen/api/v1/memo_service.pb.go
View file @
b144faf4
This diff is collapsed.
Click to expand it.
proto/gen/apidocs.swagger.yaml
View file @
b144faf4
...
@@ -2384,6 +2384,8 @@ definitions:
...
@@ -2384,6 +2384,8 @@ definitions:
items
:
items
:
type
:
object
type
:
object
$ref
:
'
#/definitions/v1MemoRelation'
$ref
:
'
#/definitions/v1MemoRelation'
location
:
$ref
:
'
#/definitions/apiv1Location'
v1CreateWebhookRequest
:
v1CreateWebhookRequest
:
type
:
object
type
:
object
properties
:
properties
:
...
...
server/router/api/v1/memo_service.go
View file @
b144faf4
...
@@ -66,6 +66,9 @@ func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoR
...
@@ -66,6 +66,9 @@ func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoR
create
.
Payload
=
&
storepb
.
MemoPayload
{
create
.
Payload
=
&
storepb
.
MemoPayload
{
Property
:
property
,
Property
:
property
,
}
}
if
request
.
Location
!=
nil
{
create
.
Payload
.
Location
=
convertLocationToStore
(
request
.
Location
)
}
memo
,
err
:=
s
.
Store
.
CreateMemo
(
ctx
,
create
)
memo
,
err
:=
s
.
Store
.
CreateMemo
(
ctx
,
create
)
if
err
!=
nil
{
if
err
!=
nil
{
...
...
web/package.json
View file @
b144faf4
...
@@ -28,6 +28,7 @@
...
@@ -28,6 +28,7 @@
"highlight.js"
:
"^11.10.0"
,
"highlight.js"
:
"^11.10.0"
,
"i18next"
:
"^23.15.1"
,
"i18next"
:
"^23.15.1"
,
"katex"
:
"^0.16.11"
,
"katex"
:
"^0.16.11"
,
"leaflet"
:
"^1.9.4"
,
"lodash-es"
:
"^4.17.21"
,
"lodash-es"
:
"^4.17.21"
,
"lucide-react"
:
"^0.437.0"
,
"lucide-react"
:
"^0.437.0"
,
"mermaid"
:
"^10.9.1"
,
"mermaid"
:
"^10.9.1"
,
...
@@ -35,6 +36,7 @@
...
@@ -35,6 +36,7 @@
"react-dom"
:
"^18.3.1"
,
"react-dom"
:
"^18.3.1"
,
"react-hot-toast"
:
"^2.4.1"
,
"react-hot-toast"
:
"^2.4.1"
,
"react-i18next"
:
"^15.0.2"
,
"react-i18next"
:
"^15.0.2"
,
"react-leaflet"
:
"^4.2.1"
,
"react-redux"
:
"^9.1.2"
,
"react-redux"
:
"^9.1.2"
,
"react-router-dom"
:
"^6.26.2"
,
"react-router-dom"
:
"^6.26.2"
,
"react-use"
:
"^17.5.1"
,
"react-use"
:
"^17.5.1"
,
...
@@ -52,6 +54,7 @@
...
@@ -52,6 +54,7 @@
"@types/d3"
:
"^7.4.3"
,
"@types/d3"
:
"^7.4.3"
,
"@types/dompurify"
:
"^3.0.5"
,
"@types/dompurify"
:
"^3.0.5"
,
"@types/katex"
:
"^0.16.7"
,
"@types/katex"
:
"^0.16.7"
,
"@types/leaflet"
:
"^1.9.12"
,
"@types/lodash-es"
:
"^4.17.12"
,
"@types/lodash-es"
:
"^4.17.12"
,
"@types/node"
:
"^22.5.5"
,
"@types/node"
:
"^22.5.5"
,
"@types/qs"
:
"^6.9.16"
,
"@types/qs"
:
"^6.9.16"
,
...
...
web/pnpm-lock.yaml
View file @
b144faf4
...
@@ -65,6 +65,9 @@ importers:
...
@@ -65,6 +65,9 @@ importers:
katex
:
katex
:
specifier
:
^0.16.11
specifier
:
^0.16.11
version
:
0.16.11
version
:
0.16.11
leaflet
:
specifier
:
^1.9.4
version
:
1.9.4
lodash-es
:
lodash-es
:
specifier
:
^4.17.21
specifier
:
^4.17.21
version
:
4.17.21
version
:
4.17.21
...
@@ -86,6 +89,9 @@ importers:
...
@@ -86,6 +89,9 @@ importers:
react-i18next
:
react-i18next
:
specifier
:
^15.0.2
specifier
:
^15.0.2
version
:
15.0.2(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version
:
15.0.2(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-leaflet
:
specifier
:
^4.2.1
version
:
4.2.1(leaflet@1.9.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-redux
:
react-redux
:
specifier
:
^9.1.2
specifier
:
^9.1.2
version
:
9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1)
version
:
9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1)
...
@@ -132,6 +138,9 @@ importers:
...
@@ -132,6 +138,9 @@ importers:
'
@types/katex'
:
'
@types/katex'
:
specifier
:
^0.16.7
specifier
:
^0.16.7
version
:
0.16.7
version
:
0.16.7
'
@types/leaflet'
:
specifier
:
^1.9.12
version
:
1.9.12
'
@types/lodash-es'
:
'
@types/lodash-es'
:
specifier
:
^4.17.12
specifier
:
^4.17.12
version
:
4.17.12
version
:
4.17.12
...
@@ -1020,6 +1029,13 @@ packages:
...
@@ -1020,6 +1029,13 @@ packages:
'
@radix-ui/rect@1.1.0'
:
'
@radix-ui/rect@1.1.0'
:
resolution
:
{
integrity
:
sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==
}
resolution
:
{
integrity
:
sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==
}
'
@react-leaflet/core@2.1.0'
:
resolution
:
{
integrity
:
sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==
}
peerDependencies
:
leaflet
:
^1.9.0
react
:
^18.0.0
react-dom
:
^18.0.0
'
@reduxjs/toolkit@2.2.7'
:
'
@reduxjs/toolkit@2.2.7'
:
resolution
:
{
integrity
:
sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==
}
resolution
:
{
integrity
:
sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==
}
peerDependencies
:
peerDependencies
:
...
@@ -1247,6 +1263,9 @@ packages:
...
@@ -1247,6 +1263,9 @@ packages:
'
@types/katex@0.16.7'
:
'
@types/katex@0.16.7'
:
resolution
:
{
integrity
:
sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==
}
resolution
:
{
integrity
:
sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==
}
'
@types/leaflet@1.9.12'
:
resolution
:
{
integrity
:
sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==
}
'
@types/lodash-es@4.17.12'
:
'
@types/lodash-es@4.17.12'
:
resolution
:
{
integrity
:
sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==
}
resolution
:
{
integrity
:
sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==
}
...
@@ -2377,6 +2396,9 @@ packages:
...
@@ -2377,6 +2396,9 @@ packages:
layout-base@1.0.2
:
layout-base@1.0.2
:
resolution
:
{
integrity
:
sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==
}
resolution
:
{
integrity
:
sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==
}
leaflet@1.9.4
:
resolution
:
{
integrity
:
sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==
}
less@4.2.0
:
less@4.2.0
:
resolution
:
{
integrity
:
sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==
}
resolution
:
{
integrity
:
sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==
}
engines
:
{
node
:
'
>=6'
}
engines
:
{
node
:
'
>=6'
}
...
@@ -2790,6 +2812,13 @@ packages:
...
@@ -2790,6 +2812,13 @@ packages:
react-is@18.3.1
:
react-is@18.3.1
:
resolution
:
{
integrity
:
sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
}
resolution
:
{
integrity
:
sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
}
react-leaflet@4.2.1
:
resolution
:
{
integrity
:
sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==
}
peerDependencies
:
leaflet
:
^1.9.0
react
:
^18.0.0
react-dom
:
^18.0.0
react-redux@9.1.2
:
react-redux@9.1.2
:
resolution
:
{
integrity
:
sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==
}
resolution
:
{
integrity
:
sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==
}
peerDependencies
:
peerDependencies
:
...
@@ -4144,6 +4173,12 @@ snapshots:
...
@@ -4144,6 +4173,12 @@ snapshots:
'
@radix-ui/rect@1.1.0'
:
{}
'
@radix-ui/rect@1.1.0'
:
{}
'
@react-leaflet/core@2.1.0(leaflet@1.9.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)'
:
dependencies
:
leaflet
:
1.9.4
react
:
18.3.1
react-dom
:
18.3.1(react@18.3.1)
'
@reduxjs/toolkit@2.2.7(react-redux@9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1))(react@18.3.1)'
:
'
@reduxjs/toolkit@2.2.7(react-redux@9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1))(react@18.3.1)'
:
dependencies
:
dependencies
:
immer
:
10.1.1
immer
:
10.1.1
...
@@ -4370,6 +4405,10 @@ snapshots:
...
@@ -4370,6 +4405,10 @@ snapshots:
'
@types/katex@0.16.7'
:
{}
'
@types/katex@0.16.7'
:
{}
'
@types/leaflet@1.9.12'
:
dependencies
:
'
@types/geojson'
:
7946.0.14
'
@types/lodash-es@4.17.12'
:
'
@types/lodash-es@4.17.12'
:
dependencies
:
dependencies
:
'
@types/lodash'
:
4.17.7
'
@types/lodash'
:
4.17.7
...
@@ -5667,6 +5706,8 @@ snapshots:
...
@@ -5667,6 +5706,8 @@ snapshots:
layout-base@1.0.2
:
{}
layout-base@1.0.2
:
{}
leaflet@1.9.4
:
{}
less@4.2.0
:
less@4.2.0
:
dependencies
:
dependencies
:
copy-anything
:
2.0.6
copy-anything
:
2.0.6
...
@@ -6177,6 +6218,13 @@ snapshots:
...
@@ -6177,6 +6218,13 @@ snapshots:
react-is@18.3.1
:
{}
react-is@18.3.1
:
{}
react-leaflet@4.2.1(leaflet@1.9.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
:
dependencies
:
'
@react-leaflet/core'
:
2.1.0(leaflet@1.9.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
leaflet
:
1.9.4
react
:
18.3.1
react-dom
:
18.3.1(react@18.3.1)
react-redux@9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1)
:
react-redux@9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1)
:
dependencies
:
dependencies
:
'
@types/use-sync-external-store'
:
0.0.3
'
@types/use-sync-external-store'
:
0.0.3
...
...
web/src/components/LeafletMap.tsx
0 → 100644
View file @
b144faf4
import
{
LatLng
}
from
"leaflet"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
MapContainer
,
Marker
,
TileLayer
,
useMapEvents
}
from
"react-leaflet"
;
interface
MarkerProps
{
position
:
LatLng
|
undefined
;
onChange
:
(
position
:
LatLng
)
=>
void
;
}
const
LocationMarker
=
(
props
:
MarkerProps
)
=>
{
const
[
position
,
setPosition
]
=
useState
(
props
.
position
);
const
map
=
useMapEvents
({
click
(
e
)
{
setPosition
(
e
.
latlng
);
map
.
locate
();
// Call the parent onChange function.
props
.
onChange
(
e
.
latlng
);
},
locationfound
()
{},
});
useEffect
(()
=>
{
map
.
attributionControl
.
setPrefix
(
""
);
map
.
locate
();
},
[]);
return
position
===
undefined
?
null
:
<
Marker
position=
{
position
}
></
Marker
>;
};
interface
MapProps
{
latlng
?:
LatLng
;
onChange
?:
(
position
:
LatLng
)
=>
void
;
}
const
LeafletMap
=
(
props
:
MapProps
)
=>
{
return
(
<
MapContainer
className=
"w-full h-72"
center=
{
props
.
latlng
}
zoom=
{
13
}
scrollWheelZoom=
{
false
}
>
<
TileLayer
url=
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution=
""
/>
<
LocationMarker
position=
{
props
.
latlng
}
onChange=
{
props
.
onChange
?
props
.
onChange
:
()
=>
{}
}
/>
</
MapContainer
>
);
};
export
default
LeafletMap
;
web/src/components/MemoEditor/ActionButton/AddMemoRelationPopover.tsx
View file @
b144faf4
...
@@ -151,7 +151,7 @@ const AddMemoRelationPopover = (props: Props) => {
...
@@ -151,7 +151,7 @@ const AddMemoRelationPopover = (props: Props) => {
getOptionLabel=
{
(
memo
)
=>
memo
.
content
}
getOptionLabel=
{
(
memo
)
=>
memo
.
content
}
isOptionEqualToValue=
{
(
memo
,
value
)
=>
memo
.
name
===
value
.
name
}
isOptionEqualToValue=
{
(
memo
,
value
)
=>
memo
.
name
===
value
.
name
}
renderOption=
{
(
props
,
memo
)
=>
(
renderOption=
{
(
props
,
memo
)
=>
(
<
AutocompleteOption
{
...
props
}
>
<
AutocompleteOption
{
...
props
}
key=
{
memo
.
name
}
>
<
div
className=
"w-full flex flex-col justify-start items-start"
>
<
div
className=
"w-full flex flex-col justify-start items-start"
>
<
p
className=
"text-xs text-gray-400 select-none"
>
{
memo
.
displayTime
?.
toLocaleString
()
}
</
p
>
<
p
className=
"text-xs text-gray-400 select-none"
>
{
memo
.
displayTime
?.
toLocaleString
()
}
</
p
>
<
p
className=
"mt-0.5 text-sm leading-5 line-clamp-2"
>
{
searchText
?
getHighlightedContent
(
memo
.
content
)
:
memo
.
snippet
}
</
p
>
<
p
className=
"mt-0.5 text-sm leading-5 line-clamp-2"
>
{
searchText
?
getHighlightedContent
(
memo
.
content
)
:
memo
.
snippet
}
</
p
>
...
...
web/src/components/MemoEditor/ActionButton/LocationSelector.tsx
0 → 100644
View file @
b144faf4
import
{
Button
,
IconButton
,
Input
}
from
"@mui/joy"
;
import
{
LatLng
}
from
"leaflet"
;
import
{
MapPinIcon
}
from
"lucide-react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
toast
from
"react-hot-toast"
;
import
LeafletMap
from
"@/components/LeafletMap"
;
import
{
Popover
,
PopoverContent
,
PopoverTrigger
}
from
"@/components/ui/Popover"
;
import
{
Location
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
interface
Props
{
location
?:
Location
;
onChange
:
(
location
:
Location
)
=>
void
;
}
interface
State
{
placeholder
:
string
;
position
:
LatLng
;
}
const
LocationSelector
=
(
props
:
Props
)
=>
{
const
t
=
useTranslate
();
const
[
state
,
setState
]
=
useState
<
State
>
({
placeholder
:
props
.
location
?.
placeholder
||
""
,
position
:
new
LatLng
(
props
.
location
?.
latitude
||
0
,
props
.
location
?.
longitude
||
0
),
});
const
[
popoverOpen
,
setPopoverOpen
]
=
useState
<
boolean
>
(
false
);
useEffect
(()
=>
{
if
(
popoverOpen
&&
!
props
.
location
)
{
const
handleError
=
(
error
:
any
,
errorMessage
:
string
)
=>
{
toast
.
error
(
errorMessage
);
console
.
error
(
error
);
};
if
(
navigator
.
geolocation
)
{
navigator
.
geolocation
.
getCurrentPosition
(
(
position
)
=>
{
const
lat
=
position
.
coords
.
latitude
;
const
lng
=
position
.
coords
.
longitude
;
setState
({
...
state
,
position
:
new
LatLng
(
lat
,
lng
)
});
},
(
error
)
=>
{
handleError
(
error
,
"Error getting current position"
);
},
);
}
else
{
handleError
(
"Geolocation is not supported by this browser."
,
"Geolocation is not supported by this browser."
);
}
}
},
[
popoverOpen
]);
useEffect
(()
=>
{
// Fetch reverse geocoding data.
fetch
(
`https://nominatim.openstreetmap.org/reverse?lat=
${
state
.
position
.
lat
}
&lon=
${
state
.
position
.
lng
}
&format=json`
)
.
then
((
response
)
=>
response
.
json
())
.
then
((
data
)
=>
{
if
(
data
&&
data
.
display_name
)
{
setState
({
...
state
,
placeholder
:
data
.
display_name
});
}
})
.
catch
((
error
)
=>
{
toast
.
error
(
"Failed to fetch reverse geocoding data"
);
console
.
error
(
"Failed to fetch reverse geocoding data:"
,
error
);
});
},
[
state
.
position
]);
const
onPositionChanged
=
(
position
:
LatLng
)
=>
{
setState
({
...
state
,
position
});
};
return
(
<
Popover
open=
{
popoverOpen
}
onOpenChange=
{
setPopoverOpen
}
>
<
PopoverTrigger
>
<
IconButton
size=
"sm"
component=
"div"
>
<
MapPinIcon
className=
"w-5 h-5 mx-auto shrink-0"
/>
{
props
.
location
&&
(
<
span
className=
"font-normal ml-0.5 text-ellipsis whitespace-nowrap overflow-hidden max-w-32"
>
{
props
.
location
.
placeholder
}
</
span
>
)
}
</
IconButton
>
</
PopoverTrigger
>
<
PopoverContent
align=
"center"
>
<
div
className=
"min-w-80 sm:w-128 flex flex-col justify-start items-start"
>
<
LeafletMap
key=
{
JSON
.
stringify
(
state
.
position
)
}
latlng=
{
state
.
position
}
onChange=
{
onPositionChanged
}
/>
<
div
className=
"mt-2 w-full flex flex-row justify-between items-center gap-2"
>
<
Input
placeholder=
"Choose location"
value=
{
state
.
placeholder
}
onChange=
{
(
e
)
=>
setState
((
state
)
=>
({
...
state
,
placeholder
:
e
.
target
.
value
}))
}
/>
<
Button
size=
"sm"
onClick=
{
()
=>
{
props
.
onChange
(
Location
.
fromPartial
({
placeholder
:
state
.
placeholder
,
latitude
:
state
.
position
.
lat
,
longitude
:
state
.
position
.
lng
,
}),
);
setPopoverOpen
(
false
);
}
}
disabled=
{
!
state
.
position
||
state
.
placeholder
.
length
===
0
}
>
{
t
(
"common.add"
)
}
</
Button
>
</
div
>
</
div
>
</
PopoverContent
>
</
Popover
>
);
};
export
default
LocationSelector
;
web/src/components/MemoEditor/index.tsx
View file @
b144faf4
...
@@ -12,7 +12,7 @@ import useAsyncEffect from "@/hooks/useAsyncEffect";
...
@@ -12,7 +12,7 @@ import useAsyncEffect from "@/hooks/useAsyncEffect";
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
useMemoStore
,
useResourceStore
,
useUserStore
,
useWorkspaceSettingStore
}
from
"@/store/v1"
;
import
{
useMemoStore
,
useResourceStore
,
useUserStore
,
useWorkspaceSettingStore
}
from
"@/store/v1"
;
import
{
MemoRelation
,
MemoRelation_Type
}
from
"@/types/proto/api/v1/memo_relation_service"
;
import
{
MemoRelation
,
MemoRelation_Type
}
from
"@/types/proto/api/v1/memo_relation_service"
;
import
{
Memo
,
Visibility
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
Location
,
Memo
,
Visibility
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
Resource
}
from
"@/types/proto/api/v1/resource_service"
;
import
{
Resource
}
from
"@/types/proto/api/v1/resource_service"
;
import
{
UserSetting
}
from
"@/types/proto/api/v1/user_service"
;
import
{
UserSetting
}
from
"@/types/proto/api/v1/user_service"
;
import
{
WorkspaceMemoRelatedSetting
}
from
"@/types/proto/api/v1/workspace_setting_service"
;
import
{
WorkspaceMemoRelatedSetting
}
from
"@/types/proto/api/v1/workspace_setting_service"
;
...
@@ -21,6 +21,7 @@ import { useTranslate } from "@/utils/i18n";
...
@@ -21,6 +21,7 @@ import { useTranslate } from "@/utils/i18n";
import
{
convertVisibilityFromString
,
convertVisibilityToString
}
from
"@/utils/memo"
;
import
{
convertVisibilityFromString
,
convertVisibilityToString
}
from
"@/utils/memo"
;
import
VisibilityIcon
from
"../VisibilityIcon"
;
import
VisibilityIcon
from
"../VisibilityIcon"
;
import
AddMemoRelationPopover
from
"./ActionButton/AddMemoRelationPopover"
;
import
AddMemoRelationPopover
from
"./ActionButton/AddMemoRelationPopover"
;
import
LocationSelector
from
"./ActionButton/LocationSelector"
;
import
MarkdownMenu
from
"./ActionButton/MarkdownMenu"
;
import
MarkdownMenu
from
"./ActionButton/MarkdownMenu"
;
import
TagSelector
from
"./ActionButton/TagSelector"
;
import
TagSelector
from
"./ActionButton/TagSelector"
;
import
UploadResourceButton
from
"./ActionButton/UploadResourceButton"
;
import
UploadResourceButton
from
"./ActionButton/UploadResourceButton"
;
...
@@ -44,9 +45,11 @@ export interface Props {
...
@@ -44,9 +45,11 @@ export interface Props {
}
}
interface
State
{
interface
State
{
initialized
:
boolean
;
memoVisibility
:
Visibility
;
memoVisibility
:
Visibility
;
resourceList
:
Resource
[];
resourceList
:
Resource
[];
relationList
:
MemoRelation
[];
relationList
:
MemoRelation
[];
location
:
Location
|
undefined
;
isUploadingResource
:
boolean
;
isUploadingResource
:
boolean
;
isRequesting
:
boolean
;
isRequesting
:
boolean
;
isComposing
:
boolean
;
isComposing
:
boolean
;
...
@@ -62,9 +65,11 @@ const MemoEditor = (props: Props) => {
...
@@ -62,9 +65,11 @@ const MemoEditor = (props: Props) => {
const
resourceStore
=
useResourceStore
();
const
resourceStore
=
useResourceStore
();
const
currentUser
=
useCurrentUser
();
const
currentUser
=
useCurrentUser
();
const
[
state
,
setState
]
=
useState
<
State
>
({
const
[
state
,
setState
]
=
useState
<
State
>
({
initialized
:
false
,
memoVisibility
:
Visibility
.
PRIVATE
,
memoVisibility
:
Visibility
.
PRIVATE
,
resourceList
:
[],
resourceList
:
[],
relationList
:
[],
relationList
:
[],
location
:
undefined
,
isUploadingResource
:
false
,
isUploadingResource
:
false
,
isRequesting
:
false
,
isRequesting
:
false
,
isComposing
:
false
,
isComposing
:
false
,
...
@@ -107,6 +112,10 @@ const MemoEditor = (props: Props) => {
...
@@ -107,6 +112,10 @@ const MemoEditor = (props: Props) => {
useAsyncEffect
(
async
()
=>
{
useAsyncEffect
(
async
()
=>
{
if
(
!
memoName
)
{
if
(
!
memoName
)
{
setState
((
prevState
)
=>
({
...
prevState
,
initialized
:
true
,
}));
return
;
return
;
}
}
...
@@ -118,11 +127,16 @@ const MemoEditor = (props: Props) => {
...
@@ -118,11 +127,16 @@ const MemoEditor = (props: Props) => {
memoVisibility
:
memo
.
visibility
,
memoVisibility
:
memo
.
visibility
,
resourceList
:
memo
.
resources
,
resourceList
:
memo
.
resources
,
relationList
:
memo
.
relations
,
relationList
:
memo
.
relations
,
location
:
memo
.
location
,
}));
}));
setDisplayTime
(
memo
.
displayTime
);
setDisplayTime
(
memo
.
displayTime
);
if
(
!
contentCache
)
{
if
(
!
contentCache
)
{
editorRef
.
current
?.
setContent
(
memo
.
content
??
""
);
editorRef
.
current
?.
setContent
(
memo
.
content
??
""
);
}
}
setState
((
prevState
)
=>
({
...
prevState
,
initialized
:
true
,
}));
}
}
},
[
memoName
]);
},
[
memoName
]);
...
@@ -311,6 +325,10 @@ const MemoEditor = (props: Props) => {
...
@@ -311,6 +325,10 @@ const MemoEditor = (props: Props) => {
updateMask
.
push
(
"relations"
);
updateMask
.
push
(
"relations"
);
memoPatch
.
relations
=
state
.
relationList
;
memoPatch
.
relations
=
state
.
relationList
;
}
}
if
(
!
isEqual
(
state
.
location
,
prevMemo
.
location
))
{
updateMask
.
push
(
"location"
);
memoPatch
.
location
=
state
.
location
;
}
const
memo
=
await
memoStore
.
updateMemo
(
memoPatch
,
updateMask
);
const
memo
=
await
memoStore
.
updateMemo
(
memoPatch
,
updateMask
);
if
(
onConfirm
)
{
if
(
onConfirm
)
{
onConfirm
(
memo
.
name
);
onConfirm
(
memo
.
name
);
...
@@ -324,6 +342,7 @@ const MemoEditor = (props: Props) => {
...
@@ -324,6 +342,7 @@ const MemoEditor = (props: Props) => {
visibility
:
state
.
memoVisibility
,
visibility
:
state
.
memoVisibility
,
resources
:
state
.
resourceList
,
resources
:
state
.
resourceList
,
relations
:
state
.
relationList
,
relations
:
state
.
relationList
,
location
:
state
.
location
,
})
})
:
memoServiceClient
:
memoServiceClient
.
createMemoComment
({
.
createMemoComment
({
...
@@ -333,6 +352,7 @@ const MemoEditor = (props: Props) => {
...
@@ -333,6 +352,7 @@ const MemoEditor = (props: Props) => {
visibility
:
state
.
memoVisibility
,
visibility
:
state
.
memoVisibility
,
resources
:
state
.
resourceList
,
resources
:
state
.
resourceList
,
relations
:
state
.
relationList
,
relations
:
state
.
relationList
,
location
:
state
.
location
,
},
},
})
})
.
then
((
memo
)
=>
memo
);
.
then
((
memo
)
=>
memo
);
...
@@ -383,6 +403,10 @@ const MemoEditor = (props: Props) => {
...
@@ -383,6 +403,10 @@ const MemoEditor = (props: Props) => {
const
allowSave
=
(
hasContent
||
state
.
resourceList
.
length
>
0
)
&&
!
state
.
isUploadingResource
&&
!
state
.
isRequesting
;
const
allowSave
=
(
hasContent
||
state
.
resourceList
.
length
>
0
)
&&
!
state
.
isUploadingResource
&&
!
state
.
isRequesting
;
if
(
!
state
.
initialized
)
{
return
null
;
}
return
(
return
(
<
MemoEditorContext
.
Provider
<
MemoEditorContext
.
Provider
value=
{
{
value=
{
{
...
@@ -435,6 +459,15 @@ const MemoEditor = (props: Props) => {
...
@@ -435,6 +459,15 @@ const MemoEditor = (props: Props) => {
<
MarkdownMenu
editorRef=
{
editorRef
}
/>
<
MarkdownMenu
editorRef=
{
editorRef
}
/>
<
UploadResourceButton
/>
<
UploadResourceButton
/>
<
AddMemoRelationPopover
editorRef=
{
editorRef
}
/>
<
AddMemoRelationPopover
editorRef=
{
editorRef
}
/>
<
LocationSelector
location=
{
state
.
location
}
onChange=
{
(
location
)
=>
setState
((
prevState
)
=>
({
...
prevState
,
location
,
}))
}
/>
</
div
>
</
div
>
</
div
>
</
div
>
<
Divider
className=
"!mt-2 opacity-40"
/>
<
Divider
className=
"!mt-2 opacity-40"
/>
...
...
web/src/components/MemoView.tsx
View file @
b144faf4
import
{
Tooltip
}
from
"@mui/joy"
;
import
{
Tooltip
}
from
"@mui/joy"
;
import
clsx
from
"clsx"
;
import
clsx
from
"clsx"
;
import
{
BookmarkIcon
,
MessageCircleMoreIcon
}
from
"lucide-react"
;
import
{
BookmarkIcon
,
M
apPinIcon
,
M
essageCircleMoreIcon
}
from
"lucide-react"
;
import
{
memo
,
useCallback
,
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
memo
,
useCallback
,
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
Link
,
useLocation
}
from
"react-router-dom"
;
import
{
Link
,
useLocation
}
from
"react-router-dom"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
...
@@ -198,6 +198,14 @@ const MemoView: React.FC<Props> = (props: Props) => {
...
@@ -198,6 +198,14 @@ const MemoView: React.FC<Props> = (props: Props) => {
onDoubleClick=
{
handleMemoContentDoubleClick
}
onDoubleClick=
{
handleMemoContentDoubleClick
}
compact=
{
props
.
compact
&&
workspaceMemoRelatedSetting
.
enableAutoCompact
}
compact=
{
props
.
compact
&&
workspaceMemoRelatedSetting
.
enableAutoCompact
}
/>
/>
{
memo
.
location
&&
(
<
p
className=
"w-full flex flex-row gap-0.5 items-center text-gray-500"
>
<
MapPinIcon
className=
"w-4 h-auto shrink-0"
/>
<
span
className=
"text-sm font-normal text-ellipsis whitespace-nowrap overflow-hidden"
>
{
memo
.
location
.
placeholder
?
memo
.
location
.
placeholder
:
`[${memo.location.latitude}, ${memo.location.longitude}]`
}
</
span
>
</
p
>
)
}
<
MemoResourceListView
resources=
{
memo
.
resources
}
/>
<
MemoResourceListView
resources=
{
memo
.
resources
}
/>
<
MemoRelationListView
memo=
{
memo
}
relations=
{
referencedMemos
}
/>
<
MemoRelationListView
memo=
{
memo
}
relations=
{
referencedMemos
}
/>
<
MemoReactionistView
memo=
{
memo
}
reactions=
{
memo
.
reactions
}
/>
<
MemoReactionistView
memo=
{
memo
}
reactions=
{
memo
.
reactions
}
/>
...
...
web/src/main.tsx
View file @
b144faf4
import
"@github/relative-time-element"
;
import
"@github/relative-time-element"
;
import
{
CssVarsProvider
}
from
"@mui/joy"
;
import
{
CssVarsProvider
}
from
"@mui/joy"
;
import
"leaflet/dist/leaflet.css"
;
import
{
createRoot
}
from
"react-dom/client"
;
import
{
createRoot
}
from
"react-dom/client"
;
import
{
Toaster
}
from
"react-hot-toast"
;
import
{
Toaster
}
from
"react-hot-toast"
;
import
{
Provider
}
from
"react-redux"
;
import
{
Provider
}
from
"react-redux"
;
...
...
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