Compare commits
90 commits
revert-664
...
main
Author | SHA1 | Date | |
---|---|---|---|
15156a624a | |||
486da7450d | |||
c58e22dd2a | |||
facddd3fe6 | |||
|
95a57110e4 | ||
|
4e5c085d5d | ||
|
32297c2834 | ||
|
b922df3766 | ||
|
f8813411ae | ||
|
b5fb75e82d | ||
|
35265fe807 | ||
|
f46f4c7e65 | ||
|
db60c13147 | ||
|
b27f6ceffd | ||
|
427bb51d30 | ||
|
2b805b1ee1 | ||
|
7882ec28e7 | ||
|
5b848b0456 | ||
|
00a1d07ae2 | ||
7a1ac02c99 | |||
e39e4f2406 | |||
9fd01826f7 | |||
d07bbb52f7 | |||
d833cf1ea9 | |||
|
cb211c9b77 | ||
|
c5a5efbf01 | ||
d19acda808 | |||
3869ea5e19 | |||
|
9d17f846f8 | ||
|
adf008a6be | ||
3c4499cb66 | |||
760d868dc2 | |||
eeccb24a92 | |||
6317422824 | |||
|
b0d6784462 | ||
|
d6013d5fa2 | ||
763a5babba | |||
2c7bfa7ab1 | |||
b43eacd22f | |||
|
3794690cef | ||
0785bc440c | |||
9cbd3472d4 | |||
bb148086ba | |||
fcf226c4ec | |||
f13ecf92ab | |||
09028f31ac | |||
|
cea70a1d42 | ||
|
19ba190ced | ||
|
ff5333c715 | ||
|
b570367142 | ||
d006c75872 | |||
d75174d064 | |||
2564098b6d | |||
|
82470ecf6b | ||
|
e2075b96d4 | ||
ee905cd277 | |||
|
82e1e4e86a | ||
|
240701ed7a | ||
|
0f8b04bf15 | ||
|
381b2088ac | ||
b89ec4b279 | |||
ffeb499b91 | |||
|
725a999e9a | ||
|
2141a26dbd | ||
|
fe568132a1 | ||
fab2a6d2b6 | |||
|
58fcf52cd4 | ||
9cff6bd7ec | |||
419d6d9813 | |||
6853d106d8 | |||
196531b342 | |||
48e29ee9fd | |||
19b4d18941 | |||
b9918347c1 | |||
8e6f0b1d10 | |||
56fc6bca3c | |||
53b593086c | |||
|
f5630ba590 | ||
|
930eb2dbde | ||
|
44c8350384 | ||
2ebc4f571d | |||
d769d44e60 | |||
|
c59eaff193 | ||
|
01011763e7 | ||
a371a84426 | |||
004c9a697c | |||
eb55b258d9 | |||
54caf6d3ef | |||
|
cab24874f7 | ||
|
e7356b3c3c |
8
.env.example
Normal file
|
@ -0,0 +1,8 @@
|
|||
# OAuth 2.0 Client ID. Create one at Google Cloud Console > APIs & Services > Credentials
|
||||
VITE_CLIENT_ID=
|
||||
|
||||
# OpenRouteService API key. Create one at OpenRouteService dev dashboard > Tokens
|
||||
VITE_OPENROUTESERVICE_API_KEY=
|
||||
|
||||
# Specify backend URL here
|
||||
VITE_BACKEND_URL=
|
3
.gitignore
vendored
|
@ -22,3 +22,6 @@ dist-ssr
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
#env
|
||||
.env
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
stages:
|
||||
- prepare
|
||||
- build
|
||||
- deploy
|
||||
|
||||
remove-old-services:
|
||||
stage: prepare
|
||||
script:
|
||||
- podman stop little-lines
|
||||
- podman rm little-lines
|
||||
|
||||
container-build:
|
||||
stage: build
|
||||
script:
|
||||
- sed -i "s/DATE/$(date -I)/g" ${CI_PROJECT_DIR}/src/views/About.vue
|
||||
- sed -i "s/VERSION/$(git log -1 --oneline | awk '{print $1}')/g" ${CI_PROJECT_DIR}/src/views/About.vue
|
||||
- podman build -t little-lines .
|
||||
|
||||
container-deploy:
|
||||
stage: deploy
|
||||
script:
|
||||
- podman run --name little-lines -p 8081:80 -d little-lines
|
||||
- podman generate systemd little-lines > ~/.config/systemd/user/little-lines.service
|
||||
- systemctl --user daemon-reload
|
||||
- systemctl --user enable little-lines
|
23
.woodpecker.yml
Normal file
|
@ -0,0 +1,23 @@
|
|||
when:
|
||||
- branch: main
|
||||
event: push
|
||||
- event: tag
|
||||
|
||||
steps:
|
||||
- name: deploy
|
||||
image: node
|
||||
commands:
|
||||
- npm i
|
||||
- npm run build
|
||||
- rm -rf /mnt/caddy-sites/little-lines.techtransthai.org/*
|
||||
- cp -r dist/* /mnt/caddy-sites/little-lines.techtransthai.org/
|
||||
volumes:
|
||||
- /media/core/Data1/Apps/caddy/sites:/mnt/caddy-sites
|
||||
environment:
|
||||
VITE_BACKEND_URL: https://little-lines-backend.techtransthai.org
|
||||
VITE_CLIENT_ID:
|
||||
from_secret: client_id
|
||||
VITE_OPENROUTESERVICE_API_KEY:
|
||||
from_secret: ors_api_key
|
||||
|
||||
|
19
Dockerfile
|
@ -1,16 +1,15 @@
|
|||
FROM nginx:alpine
|
||||
FROM docker.io/library/alpine:latest
|
||||
|
||||
# Set up environment for building
|
||||
RUN apk add yarn nodejs
|
||||
RUN apk add nodejs npm
|
||||
|
||||
# Copy files to build environment
|
||||
RUN mkdir /opt/micromobility-navigation
|
||||
COPY . /opt/micromobility-navigation
|
||||
RUN mkdir /opt/little-lines-frontend
|
||||
COPY . /opt/little-lines-frontend
|
||||
|
||||
# Start the app
|
||||
WORKDIR /opt/little-lines-frontend
|
||||
RUN npm i
|
||||
CMD npm run dev -- --host
|
||||
|
||||
# Run Vite production build
|
||||
WORKDIR /opt/micromobility-navigation
|
||||
RUN yarn
|
||||
RUN yarn run build
|
||||
|
||||
# Copy files to nginx path
|
||||
RUN cp -r dist/* icons /usr/share/nginx/html
|
||||
|
|
45
README.md
|
@ -6,9 +6,9 @@
|
|||
<a href="https://www.gnu.org/licenses/agpl-3.0.en.html">
|
||||
<img alt="License: AGPLv3" src="https://shields.io/badge/License-AGPLv3-blueviolet.svg">
|
||||
</a>
|
||||
<a href="https://gitlab.com/openKMITL/micromobility-navigation/-/pipelines">
|
||||
<img alt="Build Status" src="https://gitlab.com/openkmitl/micromobility-navigation/badges/main/pipeline.svg">
|
||||
<img alt="Service Status" src="https://status.techtransthai.org/api/badge/7/status">
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -17,11 +17,11 @@
|
|||
|
||||
<a href="https://little-lines.techtransthai.org">เข้าใช้งานเว็บแอป</a>
|
||||
•
|
||||
<a href="https://gitlab.com/openKMITL/micromobility-navigation/-/wikis/home">เอกสาร</a>
|
||||
<a href="https://www.techtransthai.org/webapps/little-lines/">โฮมเพจ</a>
|
||||
|
||||
<h5>ร่วมพูดคุยกับเรา:</h5>
|
||||
<a href="https://discord.gg/6aPemyuSzx">
|
||||
<img alt="Discord" src="https://img.shields.io/discord/1053041544845861015?label=Discord&color=blueviolet">
|
||||
<a href="https://t.me/techtransthai">
|
||||
<img alt="Telegram" src="https://img.shields.io/badge/Telegram-TechTransThai Community-blue">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
@ -41,37 +41,8 @@ Littles Lines คือระบบนำทางสำหรับล้อข
|
|||
สำหรับการพัฒนาด้วย NPM
|
||||
|
||||
```
|
||||
$ git clone https://gitlab.com/openKMITL/micromobility-navigation.git
|
||||
$ cd micromobility-navigation
|
||||
$ git clone https://gitlab.com/little-lines/frontend.git
|
||||
$ cd little-lines
|
||||
$ npm i
|
||||
$ npm run dev
|
||||
```
|
||||
|
||||
สำหรับการพัฒนาด้วย Yarn
|
||||
|
||||
```
|
||||
$ git clone https://gitlab.com/openKMITL/micromobility-navigation.git
|
||||
$ cd micromobility-navigation
|
||||
$ yarn
|
||||
$ yarn dev
|
||||
```
|
||||
|
||||
สำหรับ Production ด้วย Podman/Docker
|
||||
|
||||
```
|
||||
$ git clone https://gitlab.com/openKMITL/micromobility-navigation.git
|
||||
$ cd micromobility-navigation
|
||||
$ podman build -t littlelines:20230720 .
|
||||
$ podman run --name littlelines -p 8080:80 -d littlelines:20230720
|
||||
```
|
||||
|
||||
## Special Thanks
|
||||
|
||||
<div style="display: flex; justify-content: center; align-items: center;">
|
||||
<a href="https://discord.gg/6aPemyuSzx">
|
||||
<img src="assets/openKMITL-Community.png" alt=openKMITL" height="40">
|
||||
<a/>
|
||||
<a href="https://techtransthai.org">
|
||||
<img src="assets/ttt-org-wide-grey.svg" alt="TTT Logo" height="45">
|
||||
<a/>
|
||||
</div>
|
||||
```
|
BIN
assets/flag-filled-symbolic.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
2
assets/flag-filled-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" viewBox="0 0 16 16" width="16px"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="b"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.5"/></g></mask><clipPath id="c"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="d"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.7"/></g></mask><clipPath id="e"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="f"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.35"/></g></mask><clipPath id="g"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><path d="m 1.980469 1.003906 v 14 h 2 v -6 h 2.382812 l 0.722657 1.445313 c 0.171874 0.339843 0.519531 0.554687 0.894531 0.554687 h 5 c 0.554687 0 1 -0.449218 1 -1 v -6 c 0 -0.550781 -0.445313 -1 -1 -1 h -3.378907 l -0.726562 -1.449218 c -0.167969 -0.335938 -0.515625 -0.550782 -0.894531 -0.550782 z m 0 0" fill="#222222"/><g mask="url(#b)"><g clip-path="url(#c)" transform="matrix(1 0 0 1 -580 -600)"><path d="m 550 182 c -0.351562 0.003906 -0.695312 0.101562 -1 0.28125 v 3.4375 c 0.304688 0.179688 0.648438 0.277344 1 0.28125 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 c -0.339844 0 -0.679688 0.058594 -1 0.175781 v 6.824219 h 4 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#d)"><g clip-path="url(#e)" transform="matrix(1 0 0 1 -580 -600)"><path d="m 569 182 v 4 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 v 7 h 3 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#f)"><g clip-path="url(#g)" transform="matrix(1 0 0 1 -580 -600)"><path d="m 573 182.269531 v 3.449219 c 0.613281 -0.355469 0.996094 -1.007812 1 -1.71875 c 0 -0.714844 -0.382812 -1.375 -1 -1.730469 z m 0 4.90625 v 6.824219 h 2 v -4 c 0 -1.269531 -0.800781 -2.402344 -2 -2.824219 z m 0 0"/></g></g></svg>
|
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/map-marker-symbolic.png
Normal file
After Width: | Height: | Size: 14 KiB |
2
assets/map-marker-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" viewBox="0 0 16 16" width="16px"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="b"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.5"/></g></mask><clipPath id="c"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="d"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.7"/></g></mask><clipPath id="e"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="f"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.35"/></g></mask><clipPath id="g"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><path d="m 8 0 c -3.589844 0 -6.5 2.910156 -6.5 6.5 s 2.910156 6.496094 6.5 6.496094 c 3.589844 0.003906 6.5 -2.90625 6.5 -6.496094 s -2.910156 -6.5 -6.5 -6.5 z m 0 4 c 1.378906 0 2.5 1.117188 2.5 2.5 c 0 1.378906 -1.121094 2.5 -2.5 2.496094 c -1.378906 0 -2.5 -1.117188 -2.5 -2.496094 s 1.117188 -2.5 2.5 -2.5 z m 0 0" fill="#222222"/><path d="m 14.097656 8.746094 l -5.660156 0.230468 l -6.535156 -0.230468 c 0.6875 2.152344 4.097656 5.25 6.097656 7.25 v 0.003906 v -0.003906 c 2 -2 5.410156 -5.101563 6.097656 -7.25 z m 0 0" fill="#222222"/><g mask="url(#b)"><g clip-path="url(#c)" transform="matrix(1 0 0 1 -700 -60)"><path d="m 550 182 c -0.351562 0.003906 -0.695312 0.101562 -1 0.28125 v 3.4375 c 0.304688 0.179688 0.648438 0.277344 1 0.28125 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 c -0.339844 0 -0.679688 0.058594 -1 0.175781 v 6.824219 h 4 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#d)"><g clip-path="url(#e)" transform="matrix(1 0 0 1 -700 -60)"><path d="m 569 182 v 4 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 v 7 h 3 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#f)"><g clip-path="url(#g)" transform="matrix(1 0 0 1 -700 -60)"><path d="m 573 182.269531 v 3.449219 c 0.613281 -0.355469 0.996094 -1.007812 1 -1.71875 c 0 -0.714844 -0.382812 -1.375 -1 -1.730469 z m 0 4.90625 v 6.824219 h 2 v -4 c 0 -1.269531 -0.800781 -2.402344 -2 -2.824219 z m 0 0"/></g></g></svg>
|
After Width: | Height: | Size: 2.3 KiB |
2
icons/Adwaita/no-wheelchair.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" viewBox="0 0 16 16" width="16px"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="b"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.5"/></g></mask><clipPath id="c"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="d"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.7"/></g></mask><clipPath id="e"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="f"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.35"/></g></mask><clipPath id="g"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><g mask="url(#b)"><g clip-path="url(#c)" transform="matrix(1 0 0 1 -320 -60)"><path d="m 550 182 c -0.351562 0.003906 -0.695312 0.101562 -1 0.28125 v 3.4375 c 0.304688 0.179688 0.648438 0.277344 1 0.28125 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 c -0.339844 0 -0.679688 0.058594 -1 0.175781 v 6.824219 h 4 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#d)"><g clip-path="url(#e)" transform="matrix(1 0 0 1 -320 -60)"><path d="m 569 182 v 4 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 v 7 h 3 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#f)"><g clip-path="url(#g)" transform="matrix(1 0 0 1 -320 -60)"><path d="m 573 182.269531 v 3.449219 c 0.613281 -0.355469 0.996094 -1.007812 1 -1.71875 c 0 -0.714844 -0.382812 -1.375 -1 -1.730469 z m 0 4.90625 v 6.824219 h 2 v -4 c 0 -1.269531 -0.800781 -2.402344 -2 -2.824219 z m 0 0"/></g></g><g fill="#222222"><path d="m 2 1 c -0.265625 0 -0.519531 0.105469 -0.707031 0.292969 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 l 12 12 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 s 0.390625 -1.023437 0 -1.414062 l -12 -12 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 z m 0 0"/><path d="m 5.949219 0.125 c -0.753907 0.019531 -1.433594 0.460938 -1.753907 1.140625 c -0.207031 0.191406 -0.320312 0.457031 -0.320312 0.734375 v 0.5 c 0 0.128906 0.023438 0.253906 0.074219 0.371094 l 4.066406 6.238281 c 0.152344 0.378906 0.519531 0.625 0.929687 0.628906 l 2.234376 1.261719 l 2.046874 3.191406 c 0.144532 0.386719 0.40625 0.808594 0.824219 0.808594 h 0.824219 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 h -0.804688 l -1.257812 -3.351562 c -0.148438 -0.390626 -0.519531 -0.648438 -0.9375 -0.648438 h -3.324219 l -0.398437 -1 h 2.847656 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 h -3.648438 l -0.78125 -1.957031 c 0.847657 -0.253907 1.429688 -1.03125 1.429688 -1.917969 c 0 -1.105469 -0.894531 -2 -2 -2 c -0.015625 0 -0.03125 0 -0.046875 0 z m 0 0"/><path d="m 2.855469 6.671875 c -0.261719 0.246094 -0.5 0.519531 -0.707031 0.8125 l 1.398437 1.402344 c 0.179687 -0.320313 0.394531 -0.617188 0.660156 -0.859375 z m -1.273438 1.839844 c -0.164062 0.394531 -0.277343 0.8125 -0.339843 1.25 l 2.207031 2.203125 c -0.210938 -0.441406 -0.339844 -0.929688 -0.339844 -1.453125 c 0 -0.148438 0.027344 -0.289063 0.042969 -0.429688 z m -0.324219 2.851562 c 0.363282 2.234375 2.136719 4.007813 4.375 4.375 z m 7.710938 1.421875 c -0.246094 0.269532 -0.539062 0.484375 -0.855469 0.664063 l 1.371094 1.371093 c 0.289063 -0.210937 0.554687 -0.453124 0.796875 -0.722656 z m -3.9375 0.761719 l 2.199219 2.203125 c 0.4375 -0.066406 0.851562 -0.183594 1.238281 -0.351562 l -1.554688 -1.558594 c -0.144531 0.019531 -0.28125 0.042968 -0.429687 0.042968 c -0.523437 0 -1.015625 -0.125 -1.453125 -0.335937 z m 0 0"/></g></svg>
|
After Width: | Height: | Size: 3.6 KiB |
2
icons/Adwaita/wheelchair-limited.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" viewBox="0 0 16 16" width="16px"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="b"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.5"/></g></mask><clipPath id="c"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="d"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.7"/></g></mask><clipPath id="e"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="f"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.35"/></g></mask><clipPath id="g"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><g mask="url(#b)"><g clip-path="url(#c)" transform="matrix(1 0 0 1 -360 -60)"><path d="m 550 182 c -0.351562 0.003906 -0.695312 0.101562 -1 0.28125 v 3.4375 c 0.304688 0.179688 0.648438 0.277344 1 0.28125 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 c -0.339844 0 -0.679688 0.058594 -1 0.175781 v 6.824219 h 4 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#d)"><g clip-path="url(#e)" transform="matrix(1 0 0 1 -360 -60)"><path d="m 569 182 v 4 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 v 7 h 3 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#f)"><g clip-path="url(#g)" transform="matrix(1 0 0 1 -360 -60)"><path d="m 573 182.269531 v 3.449219 c 0.613281 -0.355469 0.996094 -1.007812 1 -1.71875 c 0 -0.714844 -0.382812 -1.375 -1 -1.730469 z m 0 4.90625 v 6.824219 h 2 v -4 c 0 -1.269531 -0.800781 -2.402344 -2 -2.824219 z m 0 0"/></g></g><g fill="#222222"><path d="m 4.875 1 c -0.550781 0 -1 0.449219 -1 1 v 0.5 c 0 0.128906 0.023438 0.253906 0.074219 0.371094 l 3 7.5 c 0.148437 0.378906 0.515625 0.628906 0.925781 0.628906 h 3.304688 l 1.257812 3.351562 c 0.148438 0.390626 0.519531 0.648438 0.9375 0.648438 h 1.5 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 h -0.804688 l -1.257812 -3.351562 c -0.148438 -0.390626 -0.519531 -0.648438 -0.9375 -0.648438 h -3.324219 l -2.675781 -6.691406 v -0.308594 c 0 -0.550781 -0.449219 -1 -1 -1 z m 0 0"/><path d="m 8 2.125 c 0 1.105469 -0.894531 2 -2 2 s -2 -0.894531 -2 -2 s 0.894531 -2 2 -2 s 2 0.894531 2 2 z m 0 0"/><path d="m 4.25 5.707031 c -1.808594 0.847657 -3.066406 2.683594 -3.066406 4.800781 c 0 2.917969 2.382812 5.304688 5.300781 5.304688 c 1.933594 0 3.582031 -1.066406 4.488281 -2.628906 l -0.488281 -1.226563 h -0.957031 c -0.539063 1.140625 -1.6875 1.925781 -3.042969 1.925781 c -1.875 0 -3.375 -1.5 -3.375 -3.375 c 0 -1.308593 0.742187 -2.421874 1.824219 -2.984374 z m 0 0" fill-opacity="0.34902"/><path d="m 7 6 c -0.550781 0 -1 0.449219 -1 1 s 0.449219 1 1 1 h 4 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 z m 0 0"/></g></svg>
|
After Width: | Height: | Size: 2.9 KiB |
|
@ -1,2 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8 0.0625 c -1.105469 0 -2 0.894531 -2 2 s 0.894531 2 2 2 s 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m -1 4.9375 s -1 0 -1 1 v 3 c 0 2 2 2 2 2 h 2 v -0.0625 h 0.371094 l 2.710937 4.515625 c 0.28125 0.472656 0.894531 0.625 1.367188 0.34375 c 0.476562 -0.285156 0.628906 -0.898437 0.34375 -1.375 l -3.289063 -5.484375 h -1.503906 v -0.9375 h 3 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 h -2.585938 l -0.707031 -0.707031 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 z m -2 1.972656 c -1.613281 0.183594 -3.027344 1.21875 -3.65625 2.742188 c -0.699219 1.679687 -0.308594 3.621094 0.976562 4.90625 c 1.28125 1.28125 3.222657 1.671875 4.902344 0.972656 c 1.523438 -0.628906 2.558594 -2.042969 2.738282 -3.65625 h -2.015626 c -0.164062 0.804688 -0.710937 1.488281 -1.488281 1.8125 c -0.9375 0.386719 -2.007812 0.171875 -2.726562 -0.546875 c -0.714844 -0.714844 -0.929688 -1.785156 -0.542969 -2.722656 c 0.324219 -0.777344 1.007812 -1.324219 1.8125 -1.484375 z m 0 0"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" viewBox="0 0 16 16" width="16px"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="b"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.5"/></g></mask><clipPath id="c"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="d"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.7"/></g></mask><clipPath id="e"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="f"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.35"/></g></mask><clipPath id="g"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><g mask="url(#b)"><g clip-path="url(#c)" transform="matrix(1 0 0 1 -340 -60)"><path d="m 550 182 c -0.351562 0.003906 -0.695312 0.101562 -1 0.28125 v 3.4375 c 0.304688 0.179688 0.648438 0.277344 1 0.28125 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 c -0.339844 0 -0.679688 0.058594 -1 0.175781 v 6.824219 h 4 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#d)"><g clip-path="url(#e)" transform="matrix(1 0 0 1 -340 -60)"><path d="m 569 182 v 4 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 v 7 h 3 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#f)"><g clip-path="url(#g)" transform="matrix(1 0 0 1 -340 -60)"><path d="m 573 182.269531 v 3.449219 c 0.613281 -0.355469 0.996094 -1.007812 1 -1.71875 c 0 -0.714844 -0.382812 -1.375 -1 -1.730469 z m 0 4.90625 v 6.824219 h 2 v -4 c 0 -1.269531 -0.800781 -2.402344 -2 -2.824219 z m 0 0"/></g></g><g fill="#222222"><path d="m 4.875 1 c -0.550781 0 -1 0.449219 -1 1 v 0.5 c 0 0.128906 0.023438 0.253906 0.074219 0.371094 l 3 7.5 c 0.148437 0.378906 0.515625 0.628906 0.925781 0.628906 h 3.304688 l 1.257812 3.351562 c 0.148438 0.390626 0.519531 0.648438 0.9375 0.648438 h 1.5 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 h -0.804688 l -1.257812 -3.351562 c -0.148438 -0.390626 -0.519531 -0.648438 -0.9375 -0.648438 h -3.324219 l -2.675781 -6.691406 v -0.308594 c 0 -0.550781 -0.449219 -1 -1 -1 z m 0 0"/><path d="m 8 2.125 c 0 1.105469 -0.894531 2 -2 2 s -2 -0.894531 -2 -2 s 0.894531 -2 2 -2 s 2 0.894531 2 2 z m 0 0"/><path d="m 4.25 5.707031 c -1.808594 0.847657 -3.066406 2.683594 -3.066406 4.800781 c 0 2.917969 2.382812 5.304688 5.300781 5.304688 c 1.933594 0 3.582031 -1.066406 4.488281 -2.628906 l -0.488281 -1.226563 h -0.957031 c -0.539063 1.140625 -1.6875 1.925781 -3.042969 1.925781 c -1.875 0 -3.375 -1.5 -3.375 -3.375 c 0 -1.308593 0.742187 -2.421874 1.824219 -2.984374 z m 0 0"/><path d="m 7 6 c -0.550781 0 -1 0.449219 -1 1 s 0.449219 1 1 1 h 4 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 z m 0 0"/></g></svg>
|
||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.9 KiB |
14
little-lines-frontend.container.example
Normal file
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=Little Lines frontend container
|
||||
|
||||
[Container]
|
||||
ContainerName=little-lines-frontend
|
||||
Image=localhost/little-lines-frontend
|
||||
PublishPort=5173:5173
|
||||
Volume=/path/to/env:/opt/little-lines-frontend/.env
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
1075
package-lock.json
generated
15
package.json
|
@ -9,18 +9,21 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.4.0",
|
||||
"ol": "^7.4.0",
|
||||
"ol-contextmenu": "^5.2.1",
|
||||
"axios": "^1.6.2",
|
||||
"dotenv": "^16.3.1",
|
||||
"ol": "^9.1.0",
|
||||
"ol-contextmenu": "^5.4.0",
|
||||
"ol-ext": "^4.0.10",
|
||||
"ol-geocoder": "^4.3.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue3-openlayers": "^1.0.0",
|
||||
"vue3-google-login": "^2.0.25",
|
||||
"vue3-google-oauth2": "^1.0.7",
|
||||
"vue3-openlayers": "^6.3.0",
|
||||
"vuetify": "^3.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"vite": "^4.4.0"
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"vite": "^5.2.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,95 +1,432 @@
|
|||
<template>
|
||||
<v-img
|
||||
class="image"
|
||||
src="https://cdn.vuetifyjs.com/images/cards/sunshine.jpg"
|
||||
cover
|
||||
></v-img>
|
||||
|
||||
<v-card
|
||||
class="stick-bottom card-height"
|
||||
class="stick-bottom card-height destination-card"
|
||||
width="100%"
|
||||
height="60vh"
|
||||
style="padding-top: 15px; font-weight:bold;"
|
||||
:title="destination.title"
|
||||
:subtitle="destination.subTitle"
|
||||
:height="cardHeight"
|
||||
style="padding-top: 15px;"
|
||||
>
|
||||
|
||||
<v-list lines="one">
|
||||
<div v-if="!showRoute">
|
||||
<v-list>
|
||||
|
||||
<v-list-item>
|
||||
<v-list-item-title class="title-text">{{nearestStructureData.display_name}}</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-avatar>
|
||||
<img :src="detial[0].icon"
|
||||
class="iconCheck"
|
||||
/>
|
||||
<img :src="wheelchairIcon" :class="wheelchairIconClass"/>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<v-list-item-title class="textCheck">{{detial[0].title}}</v-list-item-title>
|
||||
<v-list-item-subtitle ><a href="https://wiki.openstreetmap.org/wiki/Key:wheelchair" class="knowMore">{{detial[0].subtitle}}</a></v-list-item-subtitle>
|
||||
<v-list-item-title :class="wheelchairTextColorClass">{{ wheelchairAccessText }}</v-list-item-title>
|
||||
<v-list-item-subtitle ><a href="https://wiki.openstreetmap.org/wiki/Key:wheelchair" class="knowMore">เรียนรู้เพิ่มเติม</a></v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-avatar>
|
||||
<img :src="detial[1].icon"
|
||||
<img :src="findLocation"
|
||||
class="icon"
|
||||
/>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<v-list-item-title class="text-decoration-underline">{{detial[1].title}}</v-list-item-title>
|
||||
<v-list-item-title class="text-decoration-underline">{{nearestStructureData.lon}} , {{nearestStructureData.lat}}</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<!-- <v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-avatar>
|
||||
<img :src="detial[2].icon"
|
||||
<img :src="clock"
|
||||
class="icon"
|
||||
/>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<v-list-item-title>{{detial[2].title}}</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item-title class="text-decoration-underline">{{nearestStructureData.infoWheelchair}}</v-list-item-title>
|
||||
</v-list-item> -->
|
||||
|
||||
</v-list>
|
||||
|
||||
<v-card-actions class="stick-bottom btnlist-height justify-sa">
|
||||
<v-btn rounded="xl" variant="tonal" width="45vw" height="44px">
|
||||
<v-btn @click="addToFavorites" rounded="xl" variant="tonal" width="45vw" height="44px">
|
||||
เพิ่มลงในสถานที่โปรด</v-btn>
|
||||
<v-btn rounded="xl" variant="flat" class="text-white" width="45vw" height="44px" color="#f16322">
|
||||
<v-btn @click="viewRoute" rounded="xl" variant="flat" class="text-white" width="45vw" height="44px" color="#f16322">
|
||||
ดูเส้นทาง</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
<v-btn :to="{name: 'favorite'}" variant="tonal" icon="mdi-close" style="position: absolute; top: 15px; right: 15px;" />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else>
|
||||
|
||||
<v-list-item class="d-flex justify-center">
|
||||
<v-btn-toggle
|
||||
class="btn-toggle"
|
||||
mandatory
|
||||
>
|
||||
<v-btn class="btn"><v-icon class="icon-walk"></v-icon></v-btn>
|
||||
<v-btn class="btn"><v-icon class="icon-wheelchair"></v-icon></v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item class="d-flex justify-center">
|
||||
<v-toolbar
|
||||
dense
|
||||
floating
|
||||
style="width: 90vw; background-color: transparent;"
|
||||
>
|
||||
<v-icon
|
||||
style="
|
||||
background-color: transparent;
|
||||
border-radius: 10px;
|
||||
width: 12vw;
|
||||
height: 100%;
|
||||
">
|
||||
<v-icon
|
||||
class="icon-flag"
|
||||
style="background-color: transparent;"
|
||||
>
|
||||
</v-icon>
|
||||
|
||||
</v-icon>
|
||||
|
||||
<v-text-field
|
||||
hide-details
|
||||
prepend-icon="mdi-magnify"
|
||||
style="
|
||||
|
||||
border-radius: 10px;
|
||||
background-color: rgba(230, 230, 230, 1);
|
||||
padding-left: 2vw;"
|
||||
variant="invert-solo"
|
||||
:model-value="formattedLocation"
|
||||
flat
|
||||
></v-text-field>
|
||||
<v-btn
|
||||
hide-details
|
||||
style="
|
||||
background-color: rgba(230, 230, 230, 1);
|
||||
margin-left: 2vw;
|
||||
margin-right: 1vw;
|
||||
border-radius: 10px;
|
||||
height: 86%;"
|
||||
flat
|
||||
>
|
||||
<v-icon class="icon-plus"></v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item class="d-flex justify-center"
|
||||
style="overflow-x:hidden"
|
||||
>
|
||||
<v-toolbar
|
||||
dense
|
||||
floating
|
||||
style="
|
||||
width: 90vw;
|
||||
background-color: transparent;
|
||||
flex-wrap: nowrap;
|
||||
overflow-y: hidden;"
|
||||
>
|
||||
|
||||
<v-icon
|
||||
style="
|
||||
background-color: transparent;
|
||||
border-radius: 10px;
|
||||
width: 12vw;
|
||||
height: 100%;
|
||||
">
|
||||
<v-icon
|
||||
class="icon-flag"
|
||||
style="background-color: rgba(241, 99, 34, 1);"
|
||||
>
|
||||
</v-icon>
|
||||
|
||||
</v-icon>
|
||||
<v-text-field
|
||||
@click="viewPopup"
|
||||
readonly
|
||||
hide-details
|
||||
prepend-icon="mdi-magnify"
|
||||
style="
|
||||
border-radius: 10px;
|
||||
background-color: rgba(230, 230, 230, 1);
|
||||
padding-left: 2vw;
|
||||
"
|
||||
variant="invert-solo"
|
||||
flat
|
||||
:model-value="nearestStructureData.display_name"
|
||||
>
|
||||
|
||||
</v-text-field>
|
||||
<v-btn
|
||||
hide-details
|
||||
style="
|
||||
background-color: rgba(230, 230, 230, 1);
|
||||
margin-left: 2vw;
|
||||
margin-right: 1vw;
|
||||
border-radius: 10px;
|
||||
height: 86%;
|
||||
"
|
||||
flat
|
||||
>
|
||||
|
||||
<v-icon class="icon-vertical-arrows"></v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item class="d-flex justify-center">
|
||||
<v-card-text>0 นาที</v-card-text>
|
||||
</v-list-item>
|
||||
|
||||
<v-card-actions class="stick-bottom btnlist-height justify-sa">
|
||||
<v-btn @click="viewPopup" rounded="xl" variant="tonal" width="45vw" height="44px">
|
||||
ย้อนกลับ</v-btn>
|
||||
<v-btn @click="enterRoute" rounded="xl" variant="flat" class="text-white" width="45vw" height="44px" color="#f16322">
|
||||
เริ่มการนำทาง</v-btn>
|
||||
|
||||
</v-card-actions>
|
||||
</div>
|
||||
|
||||
<v-btn
|
||||
@click="closePopup"
|
||||
variant="tonal" icon="mdi-close" style="position: absolute; top: 15px; right: 15px;" />
|
||||
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import findLocation from '../../icons/Material/find-location.svg';
|
||||
import clock from '../../icons/Material/clock.svg';
|
||||
import check from '../../icons/Material/check-round.svg';
|
||||
const destination = {
|
||||
title: "อนุสาวรีย์ชัยสมรภูมิ",
|
||||
subTitle: "ราชเทวี , กรุงเทพมหานคร"
|
||||
}
|
||||
const detial = [
|
||||
{
|
||||
icon: check,
|
||||
subtitle: 'เรียนรู้เพิ่มเติม',
|
||||
title: 'Unrestricted Wheelchair access',
|
||||
import findLocation from '../../icons/Adwaita/find-location.svg';
|
||||
import clock from '../../icons/Adwaita/clock.svg';
|
||||
import check from '../../icons/Adwaita/check-round.svg';
|
||||
import cross from '../../icons/Adwaita/cross.svg';
|
||||
import wheelchair from '../../icons/Adwaita/wheelchair.svg'
|
||||
import wheelchairlimited from '../../icons/Adwaita/wheelchair-limited.svg'
|
||||
import nowheelchair from '../../icons/Adwaita/no-wheelchair.svg'
|
||||
|
||||
|
||||
// import DestinationInfoCard from '@/components/DestinationInfoCard.vue';
|
||||
// const destination = {
|
||||
// title: "อนุสาวรีย์ชัยสมรภูมิ",
|
||||
// subTitle: "ราชเทวี , กรุงเทพมหานคร"
|
||||
// }
|
||||
// const detial = [
|
||||
// {
|
||||
// icon: check,
|
||||
// subtitle: 'เรียนรู้เพิ่มเติม',
|
||||
// title: 'Unrestricted Wheelchair access',
|
||||
// },
|
||||
// {
|
||||
// icon: findLocation,
|
||||
// subtitle: '',
|
||||
// title: '13.76493, 100.53828',
|
||||
// },
|
||||
// {
|
||||
// icon: clock,
|
||||
// subtitle: '',
|
||||
// title: 'Mo-Su 11:30-22:00',
|
||||
// },
|
||||
// ]
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
export default {
|
||||
props: {
|
||||
nearestStructureData: Object,
|
||||
onClose: Function,
|
||||
infoWheelchair: String
|
||||
},
|
||||
{
|
||||
icon: findLocation,
|
||||
subtitle: '',
|
||||
title: '13.76493, 100.53828',
|
||||
data() {
|
||||
return {
|
||||
showPopup: true,
|
||||
showRoute: false,
|
||||
userLocation: null,
|
||||
isLocationRequested: false,
|
||||
|
||||
};
|
||||
},
|
||||
{
|
||||
icon: clock,
|
||||
subtitle: '',
|
||||
title: 'Mo-Su 11:30-22:00',
|
||||
computed: {
|
||||
cardHeight() {
|
||||
return this.showRoute ? '45vh' : '50vh';
|
||||
},
|
||||
formattedLocation() {
|
||||
if (this.isLocationRequested && !this.userLocation) {
|
||||
return 'Requesting location...';
|
||||
} else if (this.userLocation) {
|
||||
return `${this.userLocation.lon.toFixed(6)}, ${this.userLocation.lat.toFixed(6)}`;
|
||||
}
|
||||
return 'Location not available';
|
||||
},
|
||||
|
||||
//icon
|
||||
wheelchairIcon() {
|
||||
switch(this.nearestStructureData.infoWheelchair) {
|
||||
case 'yes':
|
||||
return wheelchair;
|
||||
case 'limited':
|
||||
return wheelchairlimited;
|
||||
case 'no':
|
||||
return nowheelchair;
|
||||
default:
|
||||
return cross;
|
||||
}
|
||||
},
|
||||
|
||||
//icon-color
|
||||
wheelchairIconClass() {
|
||||
// return this.nearestStructureData.infoWheelchair === 'limited' ? 'iconCheckLimited' : 'iconCheck';
|
||||
switch(this.nearestStructureData.infoWheelchair) {
|
||||
case 'yes':
|
||||
return 'iconCheck';
|
||||
case 'limited':
|
||||
return 'iconCheckLimited';
|
||||
case 'no':
|
||||
return 'iconCheckNo';
|
||||
default:
|
||||
return 'iconCheckDefault';
|
||||
}
|
||||
},
|
||||
|
||||
//text
|
||||
wheelchairAccessText() {
|
||||
switch(this.nearestStructureData.infoWheelchair) {
|
||||
case 'yes':
|
||||
return 'วีลแชร์สามารถเข้าถึงได้';
|
||||
case 'limited':
|
||||
return 'วีลแชร์เข้าถึงได้อย่างจำกัด';
|
||||
case 'no':
|
||||
return 'วีลแชร์ไม่สามารถเข้าถึงได้';
|
||||
default:
|
||||
return 'ไม่มีข้อมูลว่าวีลแชร์สามารถเข้าถึงได้หรือไม่';
|
||||
}
|
||||
},
|
||||
|
||||
//text-color
|
||||
|
||||
wheelchairTextColorClass() {
|
||||
switch(this.nearestStructureData.infoWheelchair) {
|
||||
case 'yes':
|
||||
return 'textColorYes';
|
||||
case 'limited':
|
||||
return 'textColorLimited';
|
||||
case 'no':
|
||||
return 'textColorNo';
|
||||
default:
|
||||
return 'textColorDefault';
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
methods: {
|
||||
closePopup() {
|
||||
this.showPopup = false;
|
||||
|
||||
this.$emit('updateRouting', {route:null,isRouting:false});
|
||||
this.isRouting = false;
|
||||
|
||||
this.onClose();
|
||||
},
|
||||
viewRoute() {
|
||||
this.getUserLocation();
|
||||
this.showRoute = true;
|
||||
},
|
||||
enterRoute() {
|
||||
console.log('Entering Route!');
|
||||
this.Routing();
|
||||
},
|
||||
getUserLocation() {
|
||||
this.isLocationRequested = true;
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(this.setLocation, this.handleLocationError)
|
||||
} else {
|
||||
console.error("Geolocation is not supported by this browser.");
|
||||
}
|
||||
},
|
||||
setLocation(position) {
|
||||
this.userLocation = {
|
||||
lat: position.coords.latitude,
|
||||
lon: position.coords.longitude
|
||||
};
|
||||
console.log('User Location:', this.userLocation);
|
||||
},
|
||||
handleLocationError(error) {
|
||||
console.error('Error getting location:', error);
|
||||
},
|
||||
viewPopup(){
|
||||
this.showPopup = true;
|
||||
this.showRoute = false;
|
||||
|
||||
this.$emit('updateRouting', {route:null,isRouting:false});
|
||||
this.isRouting = false;
|
||||
},
|
||||
addToFavorites() {
|
||||
const currentUser = JSON.parse(sessionStorage.getItem('current_user'));
|
||||
|
||||
if (currentUser) {
|
||||
console.log('Logged in. Proceed to add to favorites.');
|
||||
|
||||
fetch(`${import.meta.env.VITE_BACKEND_URL}/api/favorites/create`, {
|
||||
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: currentUser.id, // ใช้ userId ของผู้ใช้ที่ล็อกอินอยู่
|
||||
place_name: this.nearestStructureData.display_name,
|
||||
location: {
|
||||
type: "Point",
|
||||
coordinates: [parseFloat(this.nearestStructureData.lon), parseFloat(this.nearestStructureData.lat)]
|
||||
},
|
||||
wheelchair_access: 'Accessible',
|
||||
highway_type: 'Highway'
|
||||
})
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
console.log('Add to favorites success');
|
||||
return res.json();
|
||||
} else {
|
||||
throw Error(`Add to favorites failed (${res.status})`);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
} else {
|
||||
console.log('User not logged in. Unable to add to favorites.');
|
||||
this.$router.push({ name: 'login' });
|
||||
}
|
||||
},
|
||||
Routing(){
|
||||
if(!this.isRouting){
|
||||
console.log('Start routing!!');
|
||||
console.log(`nearestStructureData : ${this.nearestStructureData.lon},${this.nearestStructureData.lat}`);
|
||||
console.log(`userLocation : ${this.userLocation.lon},${this.userLocation.lat}`);
|
||||
// Make a request to OpenRouteService API for a sample route
|
||||
const apiKey = import.meta.env.VITE_OPENROUTESERVICE_API_KEY;
|
||||
const startCoord = `${this.userLocation.lon},${this.userLocation.lat}`;//'100.53860,13.76410';
|
||||
const endCoord = `${this.nearestStructureData.lon},${this.nearestStructureData.lat}`;//'100.53928,13.76526';
|
||||
|
||||
axios.get(`https://api.openrouteservice.org/v2/directions/wheelchair?api_key=${apiKey}&start=${startCoord}&end=${endCoord}`)
|
||||
.then(response => {
|
||||
const route = response.data.features[0].geometry.coordinates;
|
||||
console.log('This is route :',{route:route})
|
||||
this.$emit('updateRouting', {route:route,isRouting:true});
|
||||
this.isRouting = true;
|
||||
})
|
||||
.catch(error => {
|
||||
alert(`Can't routing`)
|
||||
console.error('Error fetching route:', error);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -129,5 +466,120 @@ const detial = [
|
|||
width: 100%;
|
||||
height: 45vh;
|
||||
}
|
||||
|
||||
.title-text
|
||||
{
|
||||
color: rgba(0, 0, 0, 1);
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 0px;
|
||||
text-decoration: none;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.sutitle-text{
|
||||
color: rgba(155, 155, 155, 1);
|
||||
font-style: normal;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 0px;
|
||||
text-decoration: none;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.icon-walk {
|
||||
background-color: #000000;
|
||||
-webkit-mask: url(icons/Adwaita/walk.svg) no-repeat center;
|
||||
mask: url(icons/Adwaita/walk.svg) no-repeat center;
|
||||
}
|
||||
.icon-wheelchair {
|
||||
background-color: #000000;
|
||||
-webkit-mask: url(icons/Adwaita/wheelchair.svg) no-repeat center;
|
||||
mask: url(icons/Adwaita/wheelchair.svg) no-repeat center;
|
||||
}
|
||||
.icon-plus {
|
||||
background-color: #000000;
|
||||
-webkit-mask: url(icons/Adwaita/plus.svg) no-repeat center;
|
||||
mask: url(icons/Adwaita/plus.svg) no-repeat center;
|
||||
}
|
||||
.icon-vertical-arrows {
|
||||
background-color: #000000;
|
||||
-webkit-mask: url(icons/Adwaita/vertical-arrows.svg) no-repeat center;
|
||||
mask: url(icons/Adwaita/vertical-arrows.svg) no-repeat center;
|
||||
}
|
||||
.icon-flag {
|
||||
background-color: #000000;
|
||||
-webkit-mask: url(icons/Adwaita/flag.svg) no-repeat center;
|
||||
mask: url(icons/Adwaita/flag.svg) no-repeat center;
|
||||
}
|
||||
|
||||
|
||||
.btn-toggle
|
||||
{
|
||||
border-radius: 10px;
|
||||
background-color: rgba(230, 230, 230, 1);
|
||||
}
|
||||
|
||||
.btn-toggle .btn
|
||||
{
|
||||
border-radius: 0px;
|
||||
background-color: rgba(230, 230, 230, 1);
|
||||
}
|
||||
|
||||
.destination-card .v-input__control
|
||||
{
|
||||
|
||||
max-height: 6vh;
|
||||
max-width: 50vw;
|
||||
}
|
||||
|
||||
.destination-card .v-field__input
|
||||
{
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.iconCheckYes {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
filter: brightness(0) saturate(100%) invert(45%) sepia(85%) saturate(380%) hue-rotate(100deg) brightness(98%) contrast(87%);
|
||||
}
|
||||
|
||||
.iconCheckLimited {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
filter: brightness(0) saturate(100%) invert(59%) sepia(84%) saturate(813%) hue-rotate(5deg) brightness(99%) contrast(92%);
|
||||
}
|
||||
|
||||
.iconCheckDefault {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
filter: brightness(0) saturate(100%) invert(59%) sepia(84%) saturate(813%) hue-rotate(5deg) brightness(99%) contrast(92%);
|
||||
}
|
||||
|
||||
.iconCheckNo {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
filter: brightness(0) saturate(100%) invert(25%) sepia(20%) saturate(5539%) hue-rotate(326deg) brightness(86%) contrast(109%);
|
||||
}
|
||||
|
||||
.textColorLimited {
|
||||
filter: brightness(0) saturate(100%) invert(59%) sepia(84%) saturate(813%) hue-rotate(5deg) brightness(99%) contrast(92%);
|
||||
}
|
||||
|
||||
.textColorNo {
|
||||
filter: brightness(0) saturate(100%) invert(25%) sepia(20%) saturate(5539%) hue-rotate(326deg) brightness(86%) contrast(109%);
|
||||
}
|
||||
|
||||
.textColorDefault {
|
||||
filter: brightness(0) saturate(100%) invert(59%) sepia(84%) saturate(813%) hue-rotate(5deg) brightness(99%) contrast(92%);
|
||||
}
|
||||
|
||||
.textColorYes {
|
||||
filter: brightness(0) saturate(100%) invert(45%) sepia(85%) saturate(380%) hue-rotate(100deg) brightness(98%) contrast(87%);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
38
src/components/NavigationMapCard.vue
Normal file
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<v-card
|
||||
class="stick-bottom card-height"
|
||||
width="100%"
|
||||
height="60vh"
|
||||
style="padding-top: 15px; font-weight:bold;"
|
||||
:title="nearestStructureData.display_name"
|
||||
:subtitle="destination.subTitle"
|
||||
>
|
||||
|
||||
</v-card>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
nearestStructureData: Object,
|
||||
onClose: Function,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showPopup: true,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closePopup() {
|
||||
this.showPopup = false;
|
||||
this.onClose();
|
||||
},
|
||||
|
||||
viewRoute() {
|
||||
this.$emit('changeComponent', { userLocation: this.userLocation, destination: this.destination });
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -8,6 +8,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import DestinationInfoCard from '@/components/DestinationInfoCard.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
nearestStructureData: Object,
|
||||
|
|
0
src/iconsets/adwaita.ts
Normal file
|
@ -1,6 +1,7 @@
|
|||
import { createApp } from 'vue'
|
||||
import '@/style.css'
|
||||
|
||||
|
||||
// Vuetify
|
||||
import 'vuetify/styles'
|
||||
import { createVuetify } from 'vuetify'
|
||||
|
@ -12,10 +13,11 @@ import OpenLayersMap from "vue3-openlayers";
|
|||
|
||||
import App from '@/App.vue'
|
||||
import router from '@/plugins/router'
|
||||
import vue3GoogleLogin from 'vue3-google-login'
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
directives,
|
||||
})
|
||||
|
||||
createApp(App).use(router).use(vuetify).use(OpenLayersMap).mount('#app')
|
||||
createApp(App).use(router).use(vuetify).use(OpenLayersMap).use(vue3GoogleLogin,{ clientId: import.meta.env.VITE_CLIENT_ID }).mount('#app')
|
|
@ -1,32 +1,88 @@
|
|||
<template>
|
||||
|
||||
<v-sheet class="d-flex justify-center">
|
||||
<v-sheet class="d-flex justify-center align-center">
|
||||
|
||||
|
||||
<v-sheet class="ma-2 pa-4 mb-auto">
|
||||
|
||||
<v-row class="ma-2" justify="centered">
|
||||
<v-sheet style="display: flex; align-items: center;justify-content: center;">
|
||||
|
||||
<v-img cover src="../../icons/LittleLines.svg"></v-img>
|
||||
</v-row>
|
||||
<img src="../../icons/LittleLines.svg">
|
||||
|
||||
</v-sheet>
|
||||
|
||||
<v-list-item
|
||||
center
|
||||
class="text-black"
|
||||
title="Little Lines"
|
||||
subtitle="openKMITL Community"
|
||||
<p class="text-center text-h4 font-weight-black">Little Lines</p>
|
||||
|
||||
<p class="text-center">TechTransThai Community</p>
|
||||
|
||||
|
||||
<v-sheet style="display: flex; align-items: center;justify-content: center;">
|
||||
<a class="versionbutton">2024.06.0</a>
|
||||
</v-sheet>
|
||||
|
||||
>
|
||||
|
||||
</v-list-item>
|
||||
<a class="versionbutton">DATE-VERSION</a>
|
||||
|
||||
<v-list class = "ma-2" density="compact" max-width="420px" min-width="200px" width="95vw">
|
||||
<v-list-item
|
||||
v-for="(item, i) in sourcecode_items"
|
||||
:key="i"
|
||||
:value="item"
|
||||
:href="'https://forge.techtransthai.org/little-lines'"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<!-- <v-icon :icon="item.icon"></v-icon> -->
|
||||
<img src="../../icons/Adwaita/right.svg">
|
||||
</template>
|
||||
<v-list-item-title v-text="item.text"></v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<v-list class = "ma-2" density="compact">
|
||||
<v-list-subheader>รายงานปัญหาและข้อเสนอแนะ</v-list-subheader>
|
||||
|
||||
<v-list-item
|
||||
v-for="(item, i) in report_items"
|
||||
:key="i"
|
||||
:value="item"
|
||||
:href="item.link"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<!-- <v-icon :icon="item.icon"></v-icon> -->
|
||||
<img src="../../icons/Adwaita/right.svg">
|
||||
</template>
|
||||
|
||||
<v-list-item-title v-text="item.text"></v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-sheet>
|
||||
|
||||
|
||||
|
||||
</v-sheet>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { getTopRight } from 'ol/extent';
|
||||
import {RouterLink} from 'vue-router';
|
||||
|
||||
const sourcecode_items = [
|
||||
{ text: 'ซอร์สโค้ด',
|
||||
icon: 'mdi-chevron-right',}
|
||||
]
|
||||
|
||||
const report_items = [
|
||||
{ text: 'Discord',
|
||||
icon: 'mdi-chevron-right',
|
||||
link: 'https://discord.gg/3tRdRE3tGv'},
|
||||
{ text: 'Facebook',
|
||||
icon: 'mdi-chevron-right',
|
||||
link: 'https://www.facebook.com'},
|
||||
{ text: 'Google Forms',
|
||||
icon: 'mdi-chevron-right',
|
||||
link: 'https://forms.google.com'},
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -45,6 +101,11 @@
|
|||
padding-left: 15px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.text-h4 {
|
||||
font-family: Cantarell, sans-serif !important;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -1,79 +1,83 @@
|
|||
<template>
|
||||
<!-- <h1>Favorite</h1> -->
|
||||
<v-card
|
||||
class="mx-auto fav-card">
|
||||
|
||||
<!-- <v-card
|
||||
class="mx-auto"
|
||||
max-width="420"
|
||||
height="600"
|
||||
>
|
||||
|
||||
<v-list lines="two">
|
||||
<v-list lines="two">
|
||||
<v-list-item
|
||||
v-for="item in items"
|
||||
:key="item.title"
|
||||
:title="item.title"
|
||||
:subtitle="item.desc"
|
||||
:prepend-avatar="pin_svg"
|
||||
></v-list-item>
|
||||
</v-list>
|
||||
v-for="(favorite, index) in favorite"
|
||||
:key="index"
|
||||
@click="handleClick(favorite)"
|
||||
>
|
||||
|
||||
</v-card> -->
|
||||
<template v-slot:prepend>
|
||||
<v-icon class="icon-pin"></v-icon>
|
||||
</template>
|
||||
|
||||
<v-card fluid
|
||||
class="mx-auto"
|
||||
max-width="500px"
|
||||
width="90vw"
|
||||
|
||||
max-height="600px"
|
||||
height="90vh"
|
||||
>
|
||||
<v-list
|
||||
item-props
|
||||
lines="three"
|
||||
>
|
||||
<v-list-item
|
||||
v-for="(item, i) in items"
|
||||
:to="{name: 'destination-info'}"
|
||||
:key="i"
|
||||
:value="item"
|
||||
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-avatar>
|
||||
<v-img
|
||||
:height="30"
|
||||
src= "./icons/Adwaita/pin.svg"
|
||||
></v-img>
|
||||
</v-avatar>
|
||||
|
||||
</template>
|
||||
|
||||
<v-list-item-title v-text="item.title"></v-list-item-title>
|
||||
<v-list-item-subtitle v-text="item.desc"></v-list-item-subtitle>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ favorite.place_name }}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-list>
|
||||
|
||||
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {RouterLink} from 'vue-router';
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const pin_svg = "./icons/Adwaita/pin.svg"
|
||||
const items = [
|
||||
{
|
||||
title: 'หอพักนักศึกษา',
|
||||
desc: 'ลาดกระบัง, กรุงเทพมหานคร',
|
||||
},
|
||||
{
|
||||
title: 'อนุสาวรีย์ชัยสมรภูมิ',
|
||||
desc: 'ราชเทวี, กรุงเทพมหานคร',
|
||||
},
|
||||
]
|
||||
export default {
|
||||
setup() {
|
||||
const favorite = ref([]);
|
||||
const router = useRouter();
|
||||
|
||||
const handleClick = (favoriteItem) => {
|
||||
console.log('Clicked:', favoriteItem);
|
||||
router.push({ name: 'home', params: { favoriteLocation: favoriteItem } });
|
||||
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const currentUser = JSON.parse(sessionStorage.getItem('current_user'));
|
||||
|
||||
if (currentUser && currentUser.id) {
|
||||
const response = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/favorites/${currentUser.id}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch favorites');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
favorite.value = data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
favorite,
|
||||
handleClick
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style >
|
||||
<style>
|
||||
.icon-pin{
|
||||
background-color: black;
|
||||
-webkit-mask: url(icons/Adwaita/pin.svg) no-repeat center;
|
||||
mask: url(icons/Adwaita/pin.svg) no-repeat center;
|
||||
|
||||
</style>
|
||||
}
|
||||
|
||||
.fav-card
|
||||
{
|
||||
margin-top: 2vh;
|
||||
max-width: 90vw;
|
||||
border-radius: 12px;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
box-shadow: 0px 1px 4px 1px rgba(0, 0, 0, 0.13);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
<template>
|
||||
|
||||
<div id="app">
|
||||
<router-view />
|
||||
<Popup
|
||||
<router-view
|
||||
class="router-view"
|
||||
/>
|
||||
|
||||
<DestinationInfoCard
|
||||
class="DestinationInfoCard"
|
||||
v-if="popupData"
|
||||
:nearestStructureData="popupData"
|
||||
:onClose="closePopup"
|
||||
@updateRouting="handleRouting"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- <searchbar/> -->
|
||||
|
@ -14,32 +20,45 @@
|
|||
<v-app-bar scroll-threshold="0"
|
||||
class="mx-auto px-auto"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="searchQuery"
|
||||
@keyup.enter="performSearch"
|
||||
@input="performSearch"
|
||||
density="compact"
|
||||
variant="solo"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
single-line
|
||||
hide-details
|
||||
></v-text-field>
|
||||
<v-btn icon>
|
||||
|
||||
<div class="flex-grow-1">
|
||||
<v-text-field
|
||||
v-model="searchQuery"
|
||||
@keyup.enter="performSearch"
|
||||
@input="performSearch"
|
||||
@click="toggleSearchBar()"
|
||||
density="compact"
|
||||
variant="solo"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
single-line
|
||||
hide-details
|
||||
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<v-btn icon @click="showSearchBar = !showSearchBar">
|
||||
<v-icon>mdi-crosshairs-gps</v-icon>
|
||||
</v-btn>
|
||||
|
||||
</v-app-bar>
|
||||
|
||||
</v-layout>
|
||||
|
||||
<div v-if="searchResults.length > 0" class="search-results">
|
||||
<ul>
|
||||
<li v-for="result in searchResults" :key="result.place_id" @click="moveToLocation(result)">
|
||||
{{ result.display_name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<v-list v-if="searchResults.length > 0 && !showSearchBar" class="search-results">
|
||||
<v-list-item
|
||||
v-for="result in searchResults"
|
||||
:key="result.place_id"
|
||||
@click="moveToLocation(result),toggleSearchBar()"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-map-marker</v-icon>
|
||||
</v-list-item-icon>
|
||||
{{ result.display_name }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<!-- <map/> -->
|
||||
<ol-map
|
||||
<ol-map
|
||||
:loadTilesWhileAnimating="true"
|
||||
:loadTilesWhileInteracting="true"
|
||||
style=
|
||||
|
@ -61,6 +80,33 @@
|
|||
<ol-tile-layer>
|
||||
<ol-source-osm />
|
||||
</ol-tile-layer>
|
||||
|
||||
<ol-vector-layer v-if="isRouting">
|
||||
<ol-source-vector>
|
||||
<!-- Line String Geometry -->
|
||||
<ol-feature>
|
||||
<ol-geom-line-string :coordinates="route"></ol-geom-line-string>
|
||||
</ol-feature>
|
||||
|
||||
<!-- Multi Point Geometry -->
|
||||
<ol-feature>
|
||||
<ol-geom-multi-point :coordinates="[
|
||||
[route[0][0],route[0][1]],
|
||||
[route[route.length-1][0],route[route.length-1][1]]
|
||||
]"></ol-geom-multi-point>
|
||||
</ol-feature>
|
||||
</ol-source-vector>
|
||||
|
||||
<!-- Style for the Line and Points -->
|
||||
<ol-style>
|
||||
<!-- Style for Line -->
|
||||
<ol-style-stroke :color="strokeColor" :width="strokeWidth"></ol-style-stroke>
|
||||
|
||||
<!-- Style for Points -->
|
||||
<ol-style-icon :src="startMarker" :scale="0.1" :anchor="[0.5, 1]"></ol-style-icon>
|
||||
</ol-style>
|
||||
</ol-vector-layer>
|
||||
|
||||
</ol-map>
|
||||
|
||||
</template>
|
||||
|
@ -68,11 +114,12 @@
|
|||
|
||||
|
||||
<script setup>
|
||||
|
||||
import searchbar from '@/components/searchbar.vue';
|
||||
import Popup from "@/components/Popup.vue"; // Import the Popup componen
|
||||
import { ref } from "vue";
|
||||
import axios from "axios";
|
||||
import DestinationInfoCard from '@/components/DestinationInfoCard.vue';
|
||||
|
||||
import startMarker from '../../assets/map-marker-symbolic.png';
|
||||
import stopMarker from '../../assets/flag-filled-symbolic.png';
|
||||
|
||||
const center = ref([100.538611, 13.764722]);
|
||||
const projection = ref("EPSG:4326");
|
||||
|
@ -81,17 +128,27 @@ const rotation = ref(0);
|
|||
|
||||
const popupData = ref(null);
|
||||
|
||||
const isRouting = ref(false);
|
||||
const route = ref(null);
|
||||
const infoWheelchair = ref(null)
|
||||
const strokeWidth = ref(5);
|
||||
const strokeColor = ref("red");
|
||||
|
||||
//search
|
||||
const searchQuery = ref("");
|
||||
const searchResults = ref([]);
|
||||
const showSearchBar = ref(false);
|
||||
|
||||
const toggleSearchBar = () => {
|
||||
showSearchBar.value = !showSearchBar.value;
|
||||
};
|
||||
|
||||
const moveToLocation = (result) => {
|
||||
// Extract latitude and longitude from the selected result
|
||||
const lat = parseFloat(result.lat);
|
||||
const lon = parseFloat(result.lon);
|
||||
|
||||
// Update the center coordinates to move the camera to the selected location
|
||||
center.value = [lon, lat];
|
||||
showSearchBar.value = false;
|
||||
popupData.value = result;
|
||||
};
|
||||
|
||||
const performSearch = async () => {
|
||||
|
@ -100,26 +157,56 @@ const performSearch = async () => {
|
|||
`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(searchQuery.value)}`
|
||||
);
|
||||
|
||||
// Process the search results and limit to, let's say, 5 results
|
||||
searchResults.value = response.data.slice(0, 5);
|
||||
} catch (error) {
|
||||
console.error("Error fetching search results:", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//Show API
|
||||
const handleMapClick = async event => {
|
||||
const clickedCoordinate = event.coordinate;
|
||||
const overpassQuery = `[out:json];
|
||||
(
|
||||
node(around:10,${clickedCoordinate[1]},${clickedCoordinate[0]})["wheelchair"];
|
||||
way(around:10,${clickedCoordinate[1]},${clickedCoordinate[0]})["wheelchair"];
|
||||
relation(around:10,${clickedCoordinate[1]},${clickedCoordinate[0]})["wheelchair"];
|
||||
);
|
||||
out;`;
|
||||
const overpassUrl = 'https://overpass-api.de/api/interpreter';
|
||||
|
||||
axios.post(overpassUrl, `data=${encodeURIComponent(overpassQuery)}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
}).then(response => {
|
||||
// Process the data returned by the Overpass API
|
||||
response.data.elements.forEach(element => {
|
||||
if (element.tags && element.tags.wheelchair) {
|
||||
console.log(`wheelchair: ${element.tags.wheelchair}`);
|
||||
const wheelchairValues = element.tags.wheelchair;
|
||||
infoWheelchair.value = wheelchairValues;
|
||||
}
|
||||
})
|
||||
}).catch(error => {
|
||||
console.error('Error fetching data from Overpass API:', error);
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${clickedCoordinate[1]}&lon=${clickedCoordinate[0]}`
|
||||
);
|
||||
|
||||
const nearestStructureData = response.data;
|
||||
popupData.value = nearestStructureData; // Show popup
|
||||
let nearestStructureData = response.data;
|
||||
|
||||
nearestStructureData = {
|
||||
...nearestStructureData,
|
||||
infoWheelchair: infoWheelchair
|
||||
}
|
||||
//infoWheelchair can null,yes,no
|
||||
popupData.value = nearestStructureData; // Show popup
|
||||
|
||||
// console.log(nearestStructureData)
|
||||
} catch (error) {
|
||||
console.error("Error fetching reverse geocoding data:", error);
|
||||
}
|
||||
|
@ -129,6 +216,14 @@ const closePopup = () => {
|
|||
popupData.value = null; // Hide popup
|
||||
};
|
||||
|
||||
const handleRouting = (res) => {
|
||||
console.log("Received Route:", res);
|
||||
route.value = res.route;
|
||||
isRouting.value = res.isRouting;
|
||||
};
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -166,4 +261,12 @@ const closePopup = () => {
|
|||
.search-results li:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.router-view{
|
||||
z-index: -15;
|
||||
}
|
||||
|
||||
.DestinationInfoCard{
|
||||
z-index: 15;
|
||||
}
|
||||
</style>
|
|
@ -3,31 +3,82 @@
|
|||
<v-app>
|
||||
<top-bar :show-back-icon="true" :page-title="pageTitle" />
|
||||
<v-main>
|
||||
<v-container>
|
||||
<v-form name="login-form">
|
||||
<div class="mb-3">
|
||||
<label for="username">Username: </label>
|
||||
<input type="text" id="username" v-model="input.username" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password">Password: </label>
|
||||
<input type="password" id="password" v-model="input.password" />
|
||||
</div>
|
||||
|
||||
</v-form>
|
||||
</v-container>
|
||||
<div class="text-center mt-8 mb-16">
|
||||
<div class="text-h4 font-weight-bold">
|
||||
ยินดีต้อนรับกลับสู่
|
||||
<div>Little Lines</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-5">
|
||||
<v-text-field
|
||||
class="email"
|
||||
v-model="input.email"
|
||||
label="อีเมล"
|
||||
variant="solo"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<img
|
||||
class="iconEdit"
|
||||
:src="edit"
|
||||
>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-text-field
|
||||
class="password"
|
||||
v-model="input.password"
|
||||
label="รหัสผ่าน"
|
||||
variant="solo"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<img
|
||||
class="iconEdit"
|
||||
:src="edit"
|
||||
>
|
||||
<img
|
||||
class="iconEyeNotLooking"
|
||||
:src="eyeNotLooking"
|
||||
>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
|
||||
<v-btn @click.prevent="login">ลงชื่อเข้าใช้</v-btn>
|
||||
<v-btn :to="{name: 'register'}">ฉันต้องการสมัครสมาชิก</v-btn>
|
||||
<v-contaioner>
|
||||
<v-row class="button">
|
||||
<v-btn @click.prevent="login" rounded="xl" variant="flat" class="text-white" width="45vw" height="44px" color="#f16322">
|
||||
ลงชื่อเข้าใช้</v-btn>
|
||||
</v-row>
|
||||
<v-row class="button">
|
||||
<v-btn @click.prevent="loginGoogle" class="text-none" rounded="xl" variant="tonal" width="45vw" height="44px">
|
||||
ลงชื่อเข้าใช้ด้วย Google</v-btn>
|
||||
</v-row>
|
||||
<v-row class="button">
|
||||
<v-btn :to="{name: 'register'}" rounded="xl" variant="tonal" width="45vw" height="44px">
|
||||
ฉันต้องการสมัครสมาชิก</v-btn>
|
||||
</v-row>
|
||||
</v-contaioner>
|
||||
|
||||
|
||||
|
||||
</v-main>
|
||||
</v-app>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import edit from '../../icons/Material/edit.svg';
|
||||
import eyeNotLooking from '../../icons/Material/eye-not-looking.svg';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {RouterLink} from 'vue-router';
|
||||
import TopBar from '@/components/TopBar.vue';
|
||||
import { VContainer } from 'vuetify/lib/components/index.mjs';
|
||||
|
||||
import { googleSdkLoaded } from "vue3-google-login";
|
||||
import axios from 'axios'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -38,20 +89,107 @@ export default {
|
|||
return {
|
||||
pageTitle: 'ลงชื่อเข้าใช้',
|
||||
input: {
|
||||
username: '',
|
||||
email: '',
|
||||
password: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
login() {
|
||||
if (this.input.username !== '' || this.input.password !== '') {
|
||||
if (this.input.email !== '' && this.input.password !== '') {
|
||||
console.log('Authenticated: Checking with Backend');
|
||||
fetch(`${import.meta.env.VITE_BACKEND_URL}/api/users/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: this.input.email,
|
||||
password: this.input.password,
|
||||
}),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
return res.json();
|
||||
} else {
|
||||
throw Error(`Login failed (${res.status})`);
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
|
||||
console.log(data.success);
|
||||
sessionStorage.setItem('current_user', JSON.stringify({id:data.user._id,username:data.user.username,email:data.user.email}));
|
||||
|
||||
this.$router.push({ name: 'home' });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
// Handle the error, e.g., display an error message to the user
|
||||
});
|
||||
} else {
|
||||
console.log('Username and Password cannot be empty');
|
||||
console.log('Email and Password cannot be empty');
|
||||
}
|
||||
},
|
||||
|
||||
loginGoogle(){
|
||||
googleSdkLoaded(google => {
|
||||
google.accounts.oauth2
|
||||
.initCodeClient({
|
||||
client_id:
|
||||
import.meta.env.VITE_CLIENT_ID,
|
||||
scope: "email profile openid",
|
||||
redirect_uri: `${import.meta.env.VITE_BACKEND_URL}/api/users/googleAuth/callback`,
|
||||
callback: response => {
|
||||
if (response.code) {
|
||||
this.sendCodeToBackend(response.code);
|
||||
}
|
||||
}
|
||||
})
|
||||
.requestCode();
|
||||
});
|
||||
},
|
||||
async sendCodeToBackend(code) {
|
||||
try {
|
||||
const headers = {
|
||||
Authorization: code
|
||||
};
|
||||
const response = await axios.post(`${import.meta.env.VITE_BACKEND_URL}/api/users/googleAuth`, null, { headers });
|
||||
const userDetails = response.data;
|
||||
console.log("User Details:", userDetails);
|
||||
this.userDetails = userDetails;
|
||||
|
||||
sessionStorage.setItem('current_user', JSON.stringify({id:userDetails.user._id}));
|
||||
this.$router.push({ name: 'home' });
|
||||
|
||||
// Redirect to the homepage ("/")
|
||||
// this.$router.push({ name: 'home' });
|
||||
} catch (error) {
|
||||
console.error("Failed to send authorization code:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.username {
|
||||
margin-bottom: -21px;
|
||||
}
|
||||
.iconEdit, .iconEyeNotLooking {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
.iconEdit {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.iconEyeNotLooking {
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.button {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,32 +3,90 @@
|
|||
<v-app>
|
||||
<top-bar :show-back-icon="true" :page-title="pageTitle" />
|
||||
<v-main>
|
||||
<v-container>
|
||||
<v-form name="register-form">
|
||||
<div class="mb-3">
|
||||
<label for="username">Username: </label>
|
||||
<input type="text" id="username" v-model="input.username" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password">Password: </label>
|
||||
<input type="password" id="password" v-model="input.password" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password">Password Confirmation: </label>
|
||||
<input type="password" id="passwordConfirm" v-model="input.passwordConfirm" />
|
||||
</div>
|
||||
|
||||
</v-form>
|
||||
</v-container>
|
||||
|
||||
<v-btn @click.prevent="login">สมัครสมาชิค</v-btn>
|
||||
<div class="text-center mt-8 mb-16">
|
||||
<div class="text-h4">
|
||||
<div class="font-weight-bold">Little Lines</div>
|
||||
</div>
|
||||
<div>ระบบนำทางสำหรับ Micromobility</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-5">
|
||||
<v-text-field
|
||||
class="email"
|
||||
v-model="input.email"
|
||||
label="อีเมล"
|
||||
variant="solo"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<img
|
||||
class="iconEdit"
|
||||
:src="edit"
|
||||
>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-text-field
|
||||
class="password"
|
||||
v-model="input.password"
|
||||
label="รหัสผ่าน"
|
||||
variant="solo"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<img
|
||||
class="iconEdit"
|
||||
:src="edit"
|
||||
>
|
||||
<img
|
||||
class="iconEyeNotLooking"
|
||||
:src="eyeNotLooking"
|
||||
>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-text-field
|
||||
class="passwordC"
|
||||
v-model="input.passwordConfirm"
|
||||
label="ยืนยันรหัสผ่าน"
|
||||
variant="solo"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<img
|
||||
class="iconEdit"
|
||||
:src="edit"
|
||||
>
|
||||
<img
|
||||
class="iconEyeNotLooking"
|
||||
:src="eyeNotLooking"
|
||||
>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
|
||||
<v-checkbox color="#F16322" class="check">
|
||||
<template v-slot:label>
|
||||
<div>ฉันได้อ่านและยอมรับ</div>
|
||||
<a href="https://www.google.co.th/?hl=th" >นโยบายความเป็นส่วนตัว</a>
|
||||
</template>
|
||||
</v-checkbox>
|
||||
|
||||
<div class="button-register">
|
||||
<v-btn @click.prevent="register" rounded="xl" variant="flat" class="text-white" width="45vw" height="44px" color="#f16322">
|
||||
สมัครสมาชิก</v-btn>
|
||||
</div>
|
||||
|
||||
|
||||
</v-main>
|
||||
</v-app>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
<script setup>
|
||||
import edit from '../../icons/Material/edit.svg';
|
||||
import eyeNotLooking from '../../icons/Material/eye-not-looking.svg';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import TopBar from '@/components/TopBar.vue';
|
||||
|
||||
export default {
|
||||
|
@ -38,24 +96,84 @@
|
|||
name: 'register',
|
||||
data() {
|
||||
return {
|
||||
pageTitle: 'สมัครสมาชิค',
|
||||
pageTitle: 'สมัครสมาชิก',
|
||||
input: {
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
passwordConfirm:''
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
login() {
|
||||
if (this.input.username !== '' || this.input.password !== '') {
|
||||
register() {
|
||||
if (this.input.username !== '' && ((this.input.password !='') && (this.input.password == this.input.passwordConfirm))) {
|
||||
console.log('Authenticated: Checking with Backend');
|
||||
fetch(`${import.meta.env.VITE_BACKEND_URL}/api/users/create`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: "temp",
|
||||
password: this.input.password,
|
||||
email: this.input.email,
|
||||
isGoogleAccount: false
|
||||
})
|
||||
})
|
||||
.then((res) => {
|
||||
if(res.ok){
|
||||
return res.json()
|
||||
}
|
||||
else{
|
||||
return res.json().then(data => {throw Error(`${data.registerStatus}`) });
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
console.log(data.registerStatus)
|
||||
this.$router.push({name : 'login'})
|
||||
})
|
||||
.catch((err) =>{
|
||||
console.log(err)
|
||||
})
|
||||
console.log("fisnished fetch");
|
||||
} else {
|
||||
console.log('Username and Password cannot be empty');
|
||||
console.log('Email and Password cannot be empty');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.user {
|
||||
background-color: aqua;
|
||||
}
|
||||
.email, .password {
|
||||
margin-bottom: -21px;
|
||||
}
|
||||
.check {
|
||||
display: flex;
|
||||
justify-content:center;
|
||||
padding-top: 5%;
|
||||
padding-bottom: 5%;
|
||||
}
|
||||
.button-register {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
-ms-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.iconEdit, .iconEyeNotLooking {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
.iconEdit {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.iconEyeNotLooking {
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<v-app>
|
||||
<v-container class="d-flex justify-center align-center">
|
||||
<div>
|
||||
<div v-if="!currentUser">
|
||||
<h1>ยังไม่ได้ลงชื่อเข้าใช้</h1>
|
||||
<h2>ลงชื่อเข้าใช้เพื่อบันทึกการตั้งค่าอย่างปลอดภัย</h2>
|
||||
<v-container class="d-flex justify-center align-center">
|
||||
<v-btn class="ma-2" width="150" :to="{name: 'login'}">ลงชื่อเข้าใช้</v-btn>
|
||||
<v-btn class="ma-2" width="150" :to="{name: 'register'}">สมัครสมาชิค</v-btn>
|
||||
<v-btn class="ma-2" width="150" :to="{name: 'register'}">สมัครสมาชิก</v-btn>
|
||||
</v-container>
|
||||
</div>
|
||||
</v-container>
|
||||
|
@ -55,7 +55,7 @@
|
|||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<v-btn class = "ma-2 mt-3" width="100%"
|
||||
<v-btn @click.prevent="logout" class = "ma-2 mt-3" width="100%"
|
||||
color="red">
|
||||
ลงชื่อออก
|
||||
</v-btn>
|
||||
|
@ -69,6 +69,10 @@
|
|||
<script setup>
|
||||
|
||||
import {RouterLink} from 'vue-router';
|
||||
import { VContainer } from 'vuetify/lib/components/index.mjs';
|
||||
import router from '@/plugins/router'
|
||||
|
||||
const currentUser = sessionStorage.getItem('current_user');
|
||||
|
||||
const account_items = [
|
||||
{ text: 'ตั้งค่าบัญชี',
|
||||
|
@ -90,5 +94,18 @@ const display_items = [
|
|||
icon: 'mdi-chevron-right',}
|
||||
]
|
||||
|
||||
function logout() {
|
||||
if (sessionStorage.getItem('current_user')){
|
||||
console.log('loging out...');
|
||||
fetch(`${import.meta.env.VITE_BACKEND_URL}/api/users/logout`, {
|
||||
method: "GET"
|
||||
}).then(()=>{
|
||||
console.log('loged out');
|
||||
sessionStorage.removeItem('current_user');
|
||||
router.push({ name: 'home' });
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
|