v1.1
This commit is contained in:
		
							
								
								
									
										5
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								.env
									
									
									
									
									
								
							| @@ -1 +1,4 @@ | |||||||
| VITE_API_BASE_URL=https://45.146.164.63:8080 | # VUE_APP_API_URL=http://31.129.106.67:8080 | ||||||
|  | # VUE_APP_GEO_URL=http://31.129.106.67:6001 | ||||||
|  | VUE_APP_API_URL=http://127.0.0.1:8080 | ||||||
|  | VUE_APP_GEO_URL=http://127.0.0.1:6001 | ||||||
| @@ -1,8 +1,8 @@ | |||||||
| <template> | <template> | ||||||
|   <Main /> |   <Main /> | ||||||
|   <StopInfo /> |   <StopInfo /> | ||||||
|   <CarrierInfo /> |   <CarrierInfo @president-appeal-toggle="presidentOpen = $event" /> | ||||||
|   <WeatherInfo /> |   <WeatherInfo :is-president-address-open="presidentOpen" /> | ||||||
|   <RouteInfo /> |   <RouteInfo /> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -22,6 +22,11 @@ export default { | |||||||
|     WeatherInfo, |     WeatherInfo, | ||||||
|     RouteInfo, |     RouteInfo, | ||||||
|   }, |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       presidentOpen: false, | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,18 +18,29 @@ body { | |||||||
|   user-select: none; |   user-select: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| .station-label-no-bg { | .station-label-no-bg, | ||||||
|  | .station-name-ru { | ||||||
|   background: transparent !important; |   background: transparent !important; | ||||||
|   border: none !important; |   border: none !important; | ||||||
|   box-shadow: none !important; |   box-shadow: none !important; | ||||||
|   font-size: 16px; |  | ||||||
|   font-weight: 600; |  | ||||||
|   color: #fff; |  | ||||||
|   /* max-width: 200px; */ |   /* max-width: 200px; */ | ||||||
|   padding: 0 !important; |   padding: 0 !important; | ||||||
|   /* text-wrap: revert; */ |   /* text-wrap: revert; */ | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .station-name-ru { | ||||||
|  |   font-size: 16px; | ||||||
|  |   font-weight: 600; | ||||||
|  |   color: #fff; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .station-name-en { | ||||||
|  |   font-size: 14px; | ||||||
|  |   color: #ffffffad; | ||||||
|  |   text-align: left; | ||||||
|  | } | ||||||
|  |  | ||||||
| .station-label-no-bg:before { | .station-label-no-bg:before { | ||||||
|   display: none !important; |   display: none !important; | ||||||
| } | } | ||||||
| @@ -40,6 +51,7 @@ body { | |||||||
|   top: 0; |   top: 0; | ||||||
|   right: 0; |   right: 0; | ||||||
|   height: 100%; |   height: 100%; | ||||||
|  |   pointer-events: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| .stopinfo .bg { | .stopinfo .bg { | ||||||
| @@ -48,6 +60,15 @@ body { | |||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   justify-content: space-between; |   justify-content: space-between; | ||||||
|  |   pointer-events: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .stopinfo .bg * { | ||||||
|  |   pointer-events: auto; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .stopinfo .container { | ||||||
|  |   margin-bottom: 75px !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| .container { | .container { | ||||||
| @@ -118,14 +139,22 @@ body { | |||||||
|   top: 18px; |   top: 18px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .landmarks-container { | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| .stoparticles { | .stoparticles { | ||||||
|  |   padding: 0 15px; | ||||||
|   height: 50px; |   height: 50px; | ||||||
|   color: #fff; |   color: #fff; | ||||||
|   display: flex; |   display: flex; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|   justify-content: center; |   justify-content: center; | ||||||
|   margin-top: auto; |   margin-top: auto; | ||||||
|   gap: 45px; |   text-align: center; | ||||||
|  |   gap: 15px; | ||||||
|  |   justify-content: space-around; | ||||||
|   background: rgb(187, 179, 170); |   background: rgb(187, 179, 170); | ||||||
|   background: linear-gradient( |   background: linear-gradient( | ||||||
|     180deg, |     180deg, | ||||||
| @@ -150,6 +179,7 @@ body { | |||||||
|   font-weight: 600; |   font-weight: 600; | ||||||
|   cursor: pointer; |   cursor: pointer; | ||||||
|   margin: 0 15px 15px 15px; |   margin: 0 15px 15px 15px; | ||||||
|  |   pointer-events: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| .stop-buttons-container { | .stop-buttons-container { | ||||||
| @@ -167,6 +197,7 @@ body { | |||||||
|   cursor: pointer; |   cursor: pointer; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   margin-top: 15px; |   margin-top: 15px; | ||||||
|  |   pointer-events: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| .white { | .white { | ||||||
| @@ -225,6 +256,7 @@ body { | |||||||
|   left: 0; |   left: 0; | ||||||
|   height: 100%; |   height: 100%; | ||||||
|   width: 350px; |   width: 350px; | ||||||
|  |   pointer-events: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| .carrierinfo .bg { | .carrierinfo .bg { | ||||||
| @@ -310,7 +342,7 @@ body { | |||||||
| .weatherinfo { | .weatherinfo { | ||||||
|   z-index: 450; |   z-index: 450; | ||||||
|   width: 225px; |   width: 225px; | ||||||
|   padding: 10px 15px; |   padding: 10px 10px; | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   align-items: center; |   align-items: center; | ||||||
| @@ -336,8 +368,9 @@ body { | |||||||
|  |  | ||||||
| .weatherinfo .date { | .weatherinfo .date { | ||||||
|   text-align: center; |   text-align: center; | ||||||
|   font-size: 18px; |   font-size: 15px; | ||||||
|   padding: 2px 0 8px 0; |   font-weight: lighter; | ||||||
|  |   padding: 0 0 8px 0; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   border-bottom: 1px solid #ffffff7a; |   border-bottom: 1px solid #ffffff7a; | ||||||
| } | } | ||||||
| @@ -346,13 +379,16 @@ body { | |||||||
|   width: 75px; |   width: 75px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .current-weather { | ||||||
|  |   padding-left: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
| .current-weather div { | .current-weather div { | ||||||
|   text-align: center; |   text-align: center; | ||||||
|   margin-top: 5px; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .temperature-celsius { | .temperature-celsius { | ||||||
|   font-size: 35px; |   font-size: 50px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .forecast-day { | .forecast-day { | ||||||
| @@ -366,7 +402,7 @@ body { | |||||||
|  |  | ||||||
| .weather-forecast { | .weather-forecast { | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   padding: 0 10px; |   padding: 0; | ||||||
|   margin-top: 10px; |   margin-top: 10px; | ||||||
|   display: flex; |   display: flex; | ||||||
|   align-items: center; |   align-items: center; | ||||||
| @@ -376,15 +412,22 @@ body { | |||||||
| .forecast { | .forecast { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   gap: 7px; |   gap: 10px; | ||||||
|  |   width: 90px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .forecast-day { | ||||||
|  |   font-size: 17px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .additional-forecast { | .additional-forecast { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   border-top: 1px solid #ffffff7a; |   border-top: 1px solid #ffffff7a; | ||||||
|   padding: 10px 0 0 7px; |   padding: 10px 0 0 3px; | ||||||
|   gap: 7px; |   gap: 7px; | ||||||
|  |   font-size: 18px; | ||||||
|  |   font-weight: bold; | ||||||
| } | } | ||||||
|  |  | ||||||
| .additional-forecast .humidity, | .additional-forecast .humidity, | ||||||
| @@ -417,7 +460,7 @@ body { | |||||||
| .route-names { | .route-names { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   gap: 15px; |   /* gap: 2px; */ | ||||||
|   padding: 0 10px; |   padding: 0 10px; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -478,7 +521,7 @@ body { | |||||||
|   position: absolute; |   position: absolute; | ||||||
|   bottom: 10px; |   bottom: 10px; | ||||||
|   left: 310px; |   left: 310px; | ||||||
|   background: rgba(0, 0, 0, 0.4); |   background: rgba(0, 0, 0, 0); | ||||||
|   border: none; |   border: none; | ||||||
|   color: #fff; |   color: #fff; | ||||||
|   font-size: 24px; |   font-size: 24px; | ||||||
| @@ -487,6 +530,7 @@ body { | |||||||
|   border-radius: 5px; |   border-radius: 5px; | ||||||
|   z-index: 1001; |   z-index: 1001; | ||||||
|   transition: left 0.3s ease; |   transition: left 0.3s ease; | ||||||
|  |   pointer-events: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| .carrierinfo .bg.hidden + .carrier-toggle { | .carrierinfo .bg.hidden + .carrier-toggle { | ||||||
| @@ -550,21 +594,6 @@ body { | |||||||
|   overflow-y: auto; |   overflow-y: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| .carrier-toggle { |  | ||||||
|   position: absolute; |  | ||||||
|   bottom: 10px; |  | ||||||
|   left: 310px; |  | ||||||
|   background: rgba(0, 0, 0, 0.4); |  | ||||||
|   border: none; |  | ||||||
|   color: #fff; |  | ||||||
|   font-size: 24px; |  | ||||||
|   cursor: pointer; |  | ||||||
|   padding: 5px 10px; |  | ||||||
|   border-radius: 5px; |  | ||||||
|   z-index: 1001; |  | ||||||
|   transition: left 0.3s ease; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .carrierinfo .bg.hidden + .carrier-toggle { | .carrierinfo .bg.hidden + .carrier-toggle { | ||||||
|   left: 10px; |   left: 10px; | ||||||
| } | } | ||||||
| @@ -576,6 +605,7 @@ body { | |||||||
|  |  | ||||||
| .bg { | .bg { | ||||||
|   transition: transform 0.3s ease; |   transition: transform 0.3s ease; | ||||||
|  |   pointer-events: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| .dropdown-name { | .dropdown-name { | ||||||
| @@ -680,16 +710,6 @@ li.checked { | |||||||
|  |  | ||||||
| .stoparticle-option { | .stoparticle-option { | ||||||
|   cursor: pointer; |   cursor: pointer; | ||||||
|   margin-right: 10px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .stoparticle-option.selected { |  | ||||||
|   text-decoration: underline; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .stoparticle-option { |  | ||||||
|   cursor: pointer; |  | ||||||
|   margin-right: 10px; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .stoparticle-option.selected { | .stoparticle-option.selected { | ||||||
| @@ -958,3 +978,21 @@ li.checked { | |||||||
| .sight-preview-wrapper { | .sight-preview-wrapper { | ||||||
|   max-height: 300px; |   max-height: 300px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .hidden { | ||||||
|  |   display: none !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .governor-appeal { | ||||||
|  |   top: 140px; | ||||||
|  |   width: 440px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .governor-appeal p { | ||||||
|  |   max-height: 400px; | ||||||
|  |   line-height: 22px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .governor-appeal h3 { | ||||||
|  |   font-size: 22px; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -589,7 +589,6 @@ | |||||||
|             clip-rule="evenodd" |             clip-rule="evenodd" | ||||||
|           /> |           /> | ||||||
|         </svg> |         </svg> | ||||||
|  |  | ||||||
|         <span class="gos-name" |         <span class="gos-name" | ||||||
|           >При поддержке Правительства <br /> |           >При поддержке Правительства <br /> | ||||||
|           Санкт-Петербурга</span |           Санкт-Петербурга</span | ||||||
| @@ -713,7 +712,7 @@ | |||||||
|       </svg> |       </svg> | ||||||
|     </button> |     </button> | ||||||
|   </div> |   </div> | ||||||
|   <div v-if="showGovernorAppeal" class="sight-preview-panel"> |   <div v-if="showGovernorAppeal" class="governor-appeal sight-preview-panel"> | ||||||
|     <img :src="governorAppealImage" v-if="governorAppealImage" /> |     <img :src="governorAppealImage" v-if="governorAppealImage" /> | ||||||
|     <h3>{{ governorAppealTitle }}</h3> |     <h3>{{ governorAppealTitle }}</h3> | ||||||
|     <p>{{ governorAppealText }}</p> |     <p>{{ governorAppealText }}</p> | ||||||
| @@ -739,16 +738,17 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import "../assets/style/main.css"; | import "../assets/style/main.css"; | ||||||
|  | import { API_URL, GEO_URL } from "../config"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: "StopInfo", |   name: "CarrierInfo", | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       stations: [], |       stations: [], | ||||||
|       showList: false, |       showList: false, | ||||||
|       sights: [], |       sights: [], | ||||||
|       showSightsList: false, |       showSightsList: false, | ||||||
|       routeId: null, |       routeId: 1, | ||||||
|       isHidden: true, |       isHidden: true, | ||||||
|       selectedSightId: null, |       selectedSightId: null, | ||||||
|       selectedSightName: "", |       selectedSightName: "", | ||||||
| @@ -763,6 +763,7 @@ export default { | |||||||
|       governorAppealId: null, |       governorAppealId: null, | ||||||
|       selectedSightWatermarkLU: "", |       selectedSightWatermarkLU: "", | ||||||
|       selectedSightWatermarkRD: "", |       selectedSightWatermarkRD: "", | ||||||
|  |       inactivityTimer: null, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
| @@ -778,16 +779,10 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async fetchStops() { |     async fetchStops() { | ||||||
|       try { |       try { | ||||||
|         const geoResponse = await fetch( |         const geoResponse = await fetch(`${GEO_URL}/v1/geolocation/context`); | ||||||
|           "http://31.129.106.67:6001/v1/geolocation/context" |  | ||||||
|         ); |  | ||||||
|         const geoData = await geoResponse.json(); |         const geoData = await geoResponse.json(); | ||||||
|         const token = this.getCookie("auth_token"); |  | ||||||
|         const routeDetailsRes = await fetch( |         const routeDetailsRes = await fetch( | ||||||
|           `https://wn-ts.krbl.ru/route/${geoData.routeId}`, |           `${API_URL}/route/${geoData.routeId}` | ||||||
|           { |  | ||||||
|             headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|           } |  | ||||||
|         ); |         ); | ||||||
|         const routeDetails = await routeDetailsRes.json(); |         const routeDetails = await routeDetailsRes.json(); | ||||||
|         const appealId = routeDetails.governor_appeal; |         const appealId = routeDetails.governor_appeal; | ||||||
| @@ -802,29 +797,18 @@ export default { | |||||||
|  |  | ||||||
|         if (appealId && appealId !== 0) { |         if (appealId && appealId !== 0) { | ||||||
|           try { |           try { | ||||||
|             const articleRes = await fetch( |             const articleRes = await fetch(`${API_URL}/article/${appealId}`); | ||||||
|               `https://wn-ts.krbl.ru/article/${appealId}`, |  | ||||||
|               { |  | ||||||
|                 headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|               } |  | ||||||
|             ); |  | ||||||
|             const articleData = await articleRes.json(); |             const articleData = await articleRes.json(); | ||||||
|             this.governorAppealTitle = articleData.heading; |             this.governorAppealTitle = articleData.heading; | ||||||
|             this.governorAppealText = articleData.body; |             this.governorAppealText = articleData.body; | ||||||
|  |  | ||||||
|             const mediaRes = await fetch( |             const mediaRes = await fetch( | ||||||
|               `https://wn-ts.krbl.ru/article/${appealId}/media`, |               `${API_URL}/article/${appealId}/media` | ||||||
|               { |  | ||||||
|                 headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|               } |  | ||||||
|             ); |             ); | ||||||
|             const mediaData = await mediaRes.json(); |             const mediaData = await mediaRes.json(); | ||||||
|             if (mediaData.length) { |             if (mediaData.length) { | ||||||
|               const imageRes = await fetch( |               const imageRes = await fetch( | ||||||
|                 `http://31.129.106.67:8080/media/${mediaData[0].id}/download`, |                 `${API_URL}/media/${mediaData[0].id}/download` | ||||||
|                 { |  | ||||||
|                   headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|                 } |  | ||||||
|               ); |               ); | ||||||
|               this.governorAppealImage = await imageRes.url; |               this.governorAppealImage = await imageRes.url; | ||||||
|             } else { |             } else { | ||||||
| @@ -846,10 +830,7 @@ export default { | |||||||
|         if (!this.routeId) throw new Error("Route number not found"); |         if (!this.routeId) throw new Error("Route number not found"); | ||||||
|  |  | ||||||
|         const stopsResponse = await fetch( |         const stopsResponse = await fetch( | ||||||
|           `https://wn-ts.krbl.ru/route/${this.routeId}/station`, |           `${API_URL}/route/${this.routeId}/station` | ||||||
|           { |  | ||||||
|             headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|           } |  | ||||||
|         ); |         ); | ||||||
|         const stopsData = await stopsResponse.json(); |         const stopsData = await stopsResponse.json(); | ||||||
|         this.stations = stopsData; |         this.stations = stopsData; | ||||||
| @@ -859,22 +840,11 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async fetchSights() { |     async fetchSights() { | ||||||
|       try { |       try { | ||||||
|         const token = this.getCookie("auth_token"); |         const response = await fetch(`${API_URL}/route/${this.routeId}/sight`); | ||||||
|         const response = await fetch( |  | ||||||
|           `https://wn-ts.krbl.ru/route/${this.routeId}/sight`, |  | ||||||
|           { |  | ||||||
|             headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|           } |  | ||||||
|         ); |  | ||||||
|         const rawSights = await response.json(); |         const rawSights = await response.json(); | ||||||
|         const detailedSights = await Promise.all( |         const detailedSights = await Promise.all( | ||||||
|           rawSights.map(async (sight) => { |           rawSights.map(async (sight) => { | ||||||
|             const detailRes = await fetch( |             const detailRes = await fetch(`${API_URL}/sight/${sight.id}`); | ||||||
|               `https://wn-ts.krbl.ru/sight/${sight.id}`, |  | ||||||
|               { |  | ||||||
|                 headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|               } |  | ||||||
|             ); |  | ||||||
|             const detail = await detailRes.json(); |             const detail = await detailRes.json(); | ||||||
|             return { id: sight.id, name: detail.name }; |             return { id: sight.id, name: detail.name }; | ||||||
|           }) |           }) | ||||||
| @@ -886,6 +856,8 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async toggleSights() { |     async toggleSights() { | ||||||
|       this.showGovernorAppeal = false; |       this.showGovernorAppeal = false; | ||||||
|  |       this.$emit("president-appeal-toggle", this.showGovernorAppeal); | ||||||
|  |  | ||||||
|       if (!this.sights.length) { |       if (!this.sights.length) { | ||||||
|         await this.fetchSights(); |         await this.fetchSights(); | ||||||
|       } |       } | ||||||
| @@ -899,45 +871,29 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async selectSight(sightId) { |     async selectSight(sightId) { | ||||||
|       this.selectedSightId = sightId; |       this.selectedSightId = sightId; | ||||||
|       const token = this.getCookie("auth_token"); |  | ||||||
|       try { |       try { | ||||||
|         const sightRes = await fetch(`https://wn-ts.krbl.ru/sight/${sightId}`, { |         const sightRes = await fetch(`${API_URL}/sight/${sightId}`); | ||||||
|           headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|         }); |  | ||||||
|         const sightData = await sightRes.json(); |         const sightData = await sightRes.json(); | ||||||
|         this.selectedSightName = sightData.name; |         this.selectedSightName = sightData.name; | ||||||
|  |  | ||||||
|         const articleId = sightData.left_article; |         const articleId = sightData.left_article; | ||||||
|  |  | ||||||
|         const articleRes = await fetch( |         const articleRes = await fetch(`${API_URL}/article/${articleId}`); | ||||||
|           `https://wn-ts.krbl.ru/article/${articleId}`, |  | ||||||
|           { |  | ||||||
|             headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|           } |  | ||||||
|         ); |  | ||||||
|         const articleData = await articleRes.json(); |         const articleData = await articleRes.json(); | ||||||
|         this.selectedSightText = articleData.body; |         this.selectedSightText = articleData.body; | ||||||
|  |  | ||||||
|         const mediaRes = await fetch( |         const mediaRes = await fetch(`${API_URL}/article/${articleId}/media`); | ||||||
|           `https://wn-ts.krbl.ru/article/${articleId}/media`, |  | ||||||
|           { |  | ||||||
|             headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|           } |  | ||||||
|         ); |  | ||||||
|         const mediaData = await mediaRes.json(); |         const mediaData = await mediaRes.json(); | ||||||
|         if (mediaData.length) { |         if (mediaData.length) { | ||||||
|           const imageRes = await fetch( |           const imageRes = await fetch( | ||||||
|             `http://31.129.106.67:8080/media/${mediaData[0].id}/download`, |             `${API_URL}/media/${mediaData[0].id}/download` | ||||||
|             { |  | ||||||
|               headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|             } |  | ||||||
|           ); |           ); | ||||||
|           this.selectedSightImage = await imageRes.url; |           this.selectedSightImage = await imageRes.url; | ||||||
|           this.selectedSightWatermarkLU = sightData.watermark_lu |           this.selectedSightWatermarkLU = sightData.watermark_lu | ||||||
|             ? `http://31.129.106.67:8080/media/${sightData.watermark_lu}/download` |             ? `${API_URL}/media/${sightData.watermark_lu}/download` | ||||||
|             : ""; |             : ""; | ||||||
|           this.selectedSightWatermarkRD = sightData.watermark_rd |           this.selectedSightWatermarkRD = sightData.watermark_rd | ||||||
|             ? `http://31.129.106.67:8080/media/${sightData.watermark_rd}/download` |             ? `${API_URL}/media/${sightData.watermark_rd}/download` | ||||||
|             : ""; |             : ""; | ||||||
|         } else { |         } else { | ||||||
|           this.selectedSightImage = ""; |           this.selectedSightImage = ""; | ||||||
| @@ -950,6 +906,8 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async toggleList() { |     async toggleList() { | ||||||
|       this.showGovernorAppeal = false; |       this.showGovernorAppeal = false; | ||||||
|  |       this.$emit("president-appeal-toggle", this.showGovernorAppeal); | ||||||
|  |  | ||||||
|       if (!this.stations.length) { |       if (!this.stations.length) { | ||||||
|         await this.fetchStops(); |         await this.fetchStops(); | ||||||
|       } |       } | ||||||
| @@ -961,6 +919,7 @@ export default { | |||||||
|       this.showSightsList = false; |       this.showSightsList = false; | ||||||
|       this.showSightPreview = false; |       this.showSightPreview = false; | ||||||
|       this.showGovernorAppeal = false; |       this.showGovernorAppeal = false; | ||||||
|  |       this.$emit("president-appeal-toggle", this.showGovernorAppeal); | ||||||
|  |  | ||||||
|       const root = document.documentElement; |       const root = document.documentElement; | ||||||
|       root.style.setProperty( |       root.style.setProperty( | ||||||
| @@ -980,6 +939,7 @@ export default { | |||||||
|           weatherInfo?.classList.remove("shifted-left"); |           weatherInfo?.classList.remove("shifted-left"); | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
|  |       this.resetInactivityTimer(); | ||||||
|     }, |     }, | ||||||
|     handleClickOutside() { |     handleClickOutside() { | ||||||
|       // пупупу |       // пупупу | ||||||
| @@ -988,10 +948,28 @@ export default { | |||||||
|       console.log("Кнопка обращения губернатора нажата"); |       console.log("Кнопка обращения губернатора нажата"); | ||||||
|       this.showGovernorAppeal = !this.showGovernorAppeal; |       this.showGovernorAppeal = !this.showGovernorAppeal; | ||||||
|       this.showSightPreview = false; |       this.showSightPreview = false; | ||||||
|  |  | ||||||
|  |       this.$emit("president-appeal-toggle", this.showGovernorAppeal); | ||||||
|  |     }, | ||||||
|  |     resetInactivityTimer() { | ||||||
|  |       clearTimeout(this.inactivityTimer); | ||||||
|  |       this.inactivityTimer = setTimeout(this.hidePanelIfActive, 300000); //  5m | ||||||
|  |     }, | ||||||
|  |     hidePanelIfActive() { | ||||||
|  |       if (!this.isHidden) { | ||||||
|  |         this.toggleCarrierInfo(); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     handleUserActivity() { | ||||||
|  |       this.resetInactivityTimer(); | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     document.addEventListener("click", this.handleClickOutside); |     document.addEventListener("click", this.handleClickOutside); | ||||||
|  |     ["mousemove", "touchstart", "keydown", "click"].forEach((evt) => | ||||||
|  |       document.addEventListener(evt, this.handleUserActivity) | ||||||
|  |     ); | ||||||
|  |     this.resetInactivityTimer(); | ||||||
|     const root = document.documentElement; |     const root = document.documentElement; | ||||||
|     root.style.setProperty("--panel-offset", "20px"); |     root.style.setProperty("--panel-offset", "20px"); | ||||||
|     const routeInfo = document.querySelector(".routeinfo"); |     const routeInfo = document.querySelector(".routeinfo"); | ||||||
| @@ -1003,6 +981,10 @@ export default { | |||||||
|     console.log("hasGovernorAppeal в mounted:", this.hasGovernorAppeal); |     console.log("hasGovernorAppeal в mounted:", this.hasGovernorAppeal); | ||||||
|   }, |   }, | ||||||
|   beforeUnmount() { |   beforeUnmount() { | ||||||
|  |     ["mousemove", "touchstart", "keydown", "click"].forEach((evt) => | ||||||
|  |       document.removeEventListener(evt, this.handleUserActivity) | ||||||
|  |     ); | ||||||
|  |     clearTimeout(this.inactivityTimer); | ||||||
|     document.removeEventListener("click", this.handleClickOutside); |     document.removeEventListener("click", this.handleClickOutside); | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -2,20 +2,64 @@ | |||||||
|   <div class="routeinfo"> |   <div class="routeinfo"> | ||||||
|     <div class="route-number">{{ routeNumber }}</div> |     <div class="route-number">{{ routeNumber }}</div> | ||||||
|     <div class="route-names"> |     <div class="route-names"> | ||||||
|       <div class="route-name" v-if="startStopName && endStopName"> |       <!-- Russian names --> | ||||||
|  |       <div class="route-name ru"> | ||||||
|         <div class="scroll-wrapper"> |         <div class="scroll-wrapper"> | ||||||
|           <div class="scroll-inner"> |           <div :class="isStartScrolling ? 'scroll-inner scrolling' : ''"> | ||||||
|             <div class="scroll-content"> |             <div class="scroll-content"> | ||||||
|               <div class="name-block"> |               <div class="name-block"> | ||||||
|                 <span class="name"> |                 <span class="name" ref="startStopRuText"> | ||||||
|                   {{ startStopName }} — {{ endStopName }}     |                   <template v-if="isStartScrolling"> | ||||||
|                   {{ startStopName }} — {{ endStopName }}     |                     {{ startStopName }}     | ||||||
|                   {{ startStopName }} — {{ endStopName }}     |                     {{ startStopName }}     {{ startStopName }} | ||||||
|  |                   </template> | ||||||
|  |                   <template v-else> | ||||||
|  |                     {{ startStopName }} | ||||||
|  |                   </template> | ||||||
|                 </span> |                 </span> | ||||||
|                 <span class="translate"> |               </div> | ||||||
|                   {{ startStopNameEn }} — {{ endStopNameEn }}     |             </div> | ||||||
|                   {{ startStopNameEn }} — {{ endStopNameEn }}     |           </div> | ||||||
|                   {{ startStopNameEn }} — {{ endStopNameEn }}     |         </div> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <div class="route-name ru"> | ||||||
|  |         <div class="scroll-wrapper"> | ||||||
|  |           <div :class="isEndScrolling ? 'scroll-inner scrolling' : ''"> | ||||||
|  |             <div class="scroll-content"> | ||||||
|  |               <div class="name-block"> | ||||||
|  |                 <span class="name" ref="endStopRuText"> | ||||||
|  |                   <template v-if="isEndScrolling"> | ||||||
|  |                     {{ endStopName }}     | ||||||
|  |                     {{ endStopName }}     {{ endStopName }} | ||||||
|  |                   </template> | ||||||
|  |                   <template v-else> | ||||||
|  |                     {{ endStopName }} | ||||||
|  |                   </template> | ||||||
|  |                 </span> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <!-- English names --> | ||||||
|  |       <div class="route-name en"> | ||||||
|  |         <div class="scroll-wrapper"> | ||||||
|  |           <div :class="isEnScrolling ? 'scroll-inner scrolling' : ''"> | ||||||
|  |             <div class="scroll-content"> | ||||||
|  |               <div class="name-block"> | ||||||
|  |                 <span class="translate" ref="enText"> | ||||||
|  |                   <template v-if="isEnScrolling"> | ||||||
|  |                     {{ startStopNameEn }} — | ||||||
|  |                     {{ endStopNameEn }}     | ||||||
|  |                     {{ startStopNameEn }} — | ||||||
|  |                     {{ endStopNameEn }}     | ||||||
|  |                     {{ startStopNameEn }} — {{ endStopNameEn }} | ||||||
|  |                   </template> | ||||||
|  |                   <template v-else> | ||||||
|  |                     {{ startStopNameEn }} — {{ endStopNameEn }} | ||||||
|  |                   </template> | ||||||
|                 </span> |                 </span> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
| @@ -27,47 +71,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import Cookies from "js-cookie"; | import { API_URL, GEO_URL } from "../config"; | ||||||
|  |  | ||||||
| async function requestAuth() { |  | ||||||
|   const response = await fetch("https://wn-ts.krbl.ru/auth/login", { |  | ||||||
|     method: "POST", |  | ||||||
|     headers: { |  | ||||||
|       "Content-Type": "application/json", |  | ||||||
|     }, |  | ||||||
|     body: JSON.stringify({ |  | ||||||
|       email: "admin", |  | ||||||
|       password: "changeme", |  | ||||||
|     }), |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const data = await response.json(); |  | ||||||
|   Cookies.set("auth_token", data.token); |  | ||||||
|   return data.token; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function checkAuth() { |  | ||||||
|   const token = Cookies.get("auth_token"); |  | ||||||
|   try { |  | ||||||
|     if (!token) { |  | ||||||
|       return await requestAuth(); |  | ||||||
|     } else { |  | ||||||
|       const response = await fetch("https://wn-ts.krbl.ru/auth/me", { |  | ||||||
|         headers: { |  | ||||||
|           Authorization: `Bearer ${token}`, |  | ||||||
|         }, |  | ||||||
|       }); |  | ||||||
|  |  | ||||||
|       if (response.status === 401) { |  | ||||||
|         return await requestAuth(); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return token; |  | ||||||
|   } catch (error) { |  | ||||||
|     console.error("Auth check failed:", error); |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: "RouteInfo", |   name: "RouteInfo", | ||||||
| @@ -78,37 +82,36 @@ export default { | |||||||
|       endStopName: "", |       endStopName: "", | ||||||
|       startStopNameEn: "", |       startStopNameEn: "", | ||||||
|       endStopNameEn: "", |       endStopNameEn: "", | ||||||
|  |       isStartScrolling: false, | ||||||
|  |       isEndScrolling: false, | ||||||
|  |       isEnScrolling: false, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|  |   watch: { | ||||||
|  |     startStopName() { | ||||||
|  |       this.$nextTick(this.checkScroll); | ||||||
|  |     }, | ||||||
|  |     endStopName() { | ||||||
|  |       this.$nextTick(this.checkScroll); | ||||||
|  |     }, | ||||||
|  |     startStopNameEn() { | ||||||
|  |       this.$nextTick(this.checkScroll); | ||||||
|  |     }, | ||||||
|  |     endStopNameEn() { | ||||||
|  |       this.$nextTick(this.checkScroll); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|   async mounted() { |   async mounted() { | ||||||
|     const contextRes = await fetch( |     const contextRes = await fetch(`${GEO_URL}/v1/geolocation/context`); | ||||||
|       "http://31.129.106.67:6001/v1/geolocation/context" |  | ||||||
|     ); |  | ||||||
|     const data = await contextRes.json(); |     const data = await contextRes.json(); | ||||||
|     this.routeNumber = data.routeNumber; |     this.routeNumber = data.routeNumber; | ||||||
|  |  | ||||||
|     const startStopId = data.startStopId; |     const startStopId = data.startStopId; | ||||||
|     const endStopId = data.endStopId; |     const endStopId = data.endStopId; | ||||||
|  |  | ||||||
|     const token = await checkAuth(); |  | ||||||
|     if (!token) { |  | ||||||
|       console.error( |  | ||||||
|         "No valid token received, skipping wn-ts.krbl.ru requests." |  | ||||||
|       ); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const [startStopRes, endStopRes] = await Promise.all([ |     const [startStopRes, endStopRes] = await Promise.all([ | ||||||
|       fetch(`https://wn-ts.krbl.ru/station/${startStopId}`, { |       fetch(`${API_URL}/station/${startStopId}`), | ||||||
|         headers: { |       fetch(`${API_URL}/station/${endStopId}`), | ||||||
|           Authorization: `Bearer ${token}`, |  | ||||||
|         }, |  | ||||||
|       }), |  | ||||||
|       fetch(`https://wn-ts.krbl.ru/station/${endStopId}`, { |  | ||||||
|         headers: { |  | ||||||
|           Authorization: `Bearer ${token}`, |  | ||||||
|         }, |  | ||||||
|       }), |  | ||||||
|     ]); |     ]); | ||||||
|  |  | ||||||
|     const startStopData = await startStopRes.json(); |     const startStopData = await startStopRes.json(); | ||||||
| @@ -118,16 +121,8 @@ export default { | |||||||
|     this.endStopName = endStopData.name; |     this.endStopName = endStopData.name; | ||||||
|  |  | ||||||
|     const [startStopEnRes, endStopEnRes] = await Promise.all([ |     const [startStopEnRes, endStopEnRes] = await Promise.all([ | ||||||
|       fetch(`https://wn-ts.krbl.ru/station/${startStopId}?lang=en`, { |       fetch(`${API_URL}/station/${startStopId}?lang=en`), | ||||||
|         headers: { |       fetch(`${API_URL}/station/${endStopId}?lang=en`), | ||||||
|           Authorization: `Bearer ${token}`, |  | ||||||
|         }, |  | ||||||
|       }), |  | ||||||
|       fetch(`https://wn-ts.krbl.ru/station/${endStopId}?lang=en`, { |  | ||||||
|         headers: { |  | ||||||
|           Authorization: `Bearer ${token}`, |  | ||||||
|         }, |  | ||||||
|       }), |  | ||||||
|     ]); |     ]); | ||||||
|  |  | ||||||
|     const startStopEnData = await startStopEnRes.json(); |     const startStopEnData = await startStopEnRes.json(); | ||||||
| @@ -135,7 +130,45 @@ export default { | |||||||
|  |  | ||||||
|     this.startStopNameEn = startStopEnData.name; |     this.startStopNameEn = startStopEnData.name; | ||||||
|     this.endStopNameEn = endStopEnData.name; |     this.endStopNameEn = endStopEnData.name; | ||||||
|  |  | ||||||
|  |     this.$nextTick(() => { | ||||||
|  |       this.checkScroll(); | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     checkScroll() { | ||||||
|  |       const threshold = 280; | ||||||
|  |       if (this.$refs.startStopRuText) { | ||||||
|  |         this.isStartScrolling = | ||||||
|  |           this.$refs.startStopRuText.offsetWidth > threshold; | ||||||
|  |       } | ||||||
|  |       if (this.$refs.endStopRuText) { | ||||||
|  |         this.isEndScrolling = this.$refs.endStopRuText.offsetWidth > threshold; | ||||||
|  |       } | ||||||
|  |       if (this.$refs.enText) { | ||||||
|  |         this.isEnScrolling = this.$refs.enText.offsetWidth > threshold; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|   methods: {}, |  | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|  | <style scoped> | ||||||
|  | .route-name { | ||||||
|  |   overflow: hidden; | ||||||
|  |   width: 280px; | ||||||
|  |   white-space: nowrap; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .scroll-inner.scrolling { | ||||||
|  |   animation: marquee 12s linear infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes marquee { | ||||||
|  |   0% { | ||||||
|  |     transform: translateX(0); | ||||||
|  |   } | ||||||
|  |   100% { | ||||||
|  |     transform: translateX(-50%); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </style> | ||||||
|   | |||||||
| @@ -98,7 +98,7 @@ | |||||||
|         </div> |         </div> | ||||||
|       </transition> |       </transition> | ||||||
|       <transition name="expand-collapse-transition"> |       <transition name="expand-collapse-transition"> | ||||||
|         <div> |         <div class="landmarks-container"> | ||||||
|           <div class="landmarks" @click="toggleSightsList"> |           <div class="landmarks" @click="toggleSightsList"> | ||||||
|             <div class="landmarks-header"> |             <div class="landmarks-header"> | ||||||
|               <svg |               <svg | ||||||
| @@ -149,10 +149,7 @@ | |||||||
|                     :key="sight.id" |                     :key="sight.id" | ||||||
|                     class="sight-card" |                     class="sight-card" | ||||||
|                   > |                   > | ||||||
|                     <img |                     <img class="sight-thumbnail" :src="sight.thumbnailUrl" /> | ||||||
|                       class="sight-thumbnail" |  | ||||||
|                       :src="`http://31.129.106.67:8080/media/${sight.thumbnail}/download`" |  | ||||||
|                     /> |  | ||||||
|                     <div class="sight-title">{{ sight.name }}</div> |                     <div class="sight-title">{{ sight.name }}</div> | ||||||
|                   </div> |                   </div> | ||||||
|                 </div> |                 </div> | ||||||
| @@ -167,8 +164,9 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import "../assets/style/main.css"; | import "../assets/style/main.css"; | ||||||
|  | import { API_URL, GEO_URL } from "../config"; | ||||||
|  |  | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
| import Cookies from "js-cookie"; |  | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: "StopInfo", |   name: "StopInfo", | ||||||
| @@ -176,6 +174,8 @@ export default { | |||||||
|     return { |     return { | ||||||
|       isModalOpen: false, |       isModalOpen: false, | ||||||
|       autoCloseTimer: null, |       autoCloseTimer: null, | ||||||
|  |       articleInactivityTimer: null, | ||||||
|  |       sightsInactivityTimer: null, | ||||||
|       imageUrl: "", |       imageUrl: "", | ||||||
|       defaultImageUrl: |       defaultImageUrl: | ||||||
|         "https://lh3.googleusercontent.com/gps-cs-s/AB5caB8lUwofb2NIg6n0-cEl8nIWsySAUc52KNj4XezuOdo-aeqTgQlD1kTVa5MaynL2Yg4ByoTYTKNTR7K59f7kjzU9yzpudstjRiT2F6M_ilxFYFpcvMZz6OwlRFF2MrsCPSwUa7vqew=s680-w680-h510", |         "https://lh3.googleusercontent.com/gps-cs-s/AB5caB8lUwofb2NIg6n0-cEl8nIWsySAUc52KNj4XezuOdo-aeqTgQlD1kTVa5MaynL2Yg4ByoTYTKNTR7K59f7kjzU9yzpudstjRiT2F6M_ilxFYFpcvMZz6OwlRFF2MrsCPSwUa7vqew=s680-w680-h510", | ||||||
| @@ -246,41 +246,6 @@ export default { | |||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     async checkAuth() { |  | ||||||
|       let token = Cookies.get("auth_token"); |  | ||||||
|       if (!token) { |  | ||||||
|         await this.requestAuth(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       try { |  | ||||||
|         await axios.get("https://wn-ts.krbl.ru/auth/me", { |  | ||||||
|           headers: { |  | ||||||
|             Authorization: `Bearer ${token}`, |  | ||||||
|           }, |  | ||||||
|         }); |  | ||||||
|       } catch (e) { |  | ||||||
|         await this.requestAuth(); |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     async requestAuth() { |  | ||||||
|       const response = await axios.post("https://wn-ts.krbl.ru/auth/login", { |  | ||||||
|         email: "admin", |  | ||||||
|         password: "changeme", |  | ||||||
|       }); |  | ||||||
|       const token = response.data.token; |  | ||||||
|       Cookies.set("auth_token", token); |  | ||||||
|       document.cookie = `auth_token=${token}; path=/;`; |  | ||||||
|     }, |  | ||||||
|     getCookie(name) { |  | ||||||
|       const matches = document.cookie.match( |  | ||||||
|         new RegExp( |  | ||||||
|           "(?:^|; )" + |  | ||||||
|             name.replace(/([.$?*|{}()[\]\\/+^])/g, "\\$1") + |  | ||||||
|             "=([^;]*)" |  | ||||||
|         ) |  | ||||||
|       ); |  | ||||||
|       return matches ? decodeURIComponent(matches[1]) : undefined; |  | ||||||
|     }, |  | ||||||
|     openModal() { |     openModal() { | ||||||
|       const imageDiv = this.$el.querySelector(".img"); |       const imageDiv = this.$el.querySelector(".img"); | ||||||
|       if (imageDiv) { |       if (imageDiv) { | ||||||
| @@ -301,74 +266,59 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async fetchSightInfo() { |     async fetchSightInfo() { | ||||||
|       if (!this.sightId) { |       if (!this.sightId) { | ||||||
|         console.warn("No sightId provided for fetchSightInfo"); |         console.log("No sightId provided for fetchSightInfo"); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       const token = this.getCookie("auth_token"); |       const response = await axios.get(`${API_URL}/sight/${this.sightId}`); | ||||||
|       const response = await axios.get( |  | ||||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}`, |  | ||||||
|         { |  | ||||||
|           headers: { |  | ||||||
|             Authorization: `Bearer ${token}`, |  | ||||||
|           }, |  | ||||||
|         } |  | ||||||
|       ); |  | ||||||
|       this.stopName = response.data.name; |       this.stopName = response.data.name; | ||||||
|       this.watermarkLU = response.data.watermark_lu |       if (response.data.watermark_lu) { | ||||||
|         ? `http://31.129.106.67:8080/media/${response.data.watermark_lu}/download` |         this.watermarkLU = await this.getMediaBlobUrl( | ||||||
|         : ""; |           response.data.watermark_lu | ||||||
|       this.watermarkRD = response.data.watermark_rd |         ); | ||||||
|         ? `http://31.129.106.67:8080/media/${response.data.watermark_rd}/download` |       } else { | ||||||
|         : ""; |         this.watermarkLU = ""; | ||||||
|  |       } | ||||||
|  |       if (response.data.watermark_rd) { | ||||||
|  |         this.watermarkRD = await this.getMediaBlobUrl( | ||||||
|  |           response.data.watermark_rd | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         this.watermarkRD = ""; | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     async fetchArticles() { |     async fetchArticles() { | ||||||
|       if (!this.sightId) { |       if (!this.sightId) { | ||||||
|         console.warn("No sightId provided for fetchArticles"); |         console.warn("No sightId provided for fetchArticles"); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       const token = this.getCookie("auth_token"); |  | ||||||
|       const response = await axios.get( |       const response = await axios.get( | ||||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}/article`, |         `${API_URL}/sight/${this.sightId}/article` | ||||||
|         { |  | ||||||
|           headers: { |  | ||||||
|             Authorization: `Bearer ${token}`, |  | ||||||
|           }, |  | ||||||
|         } |  | ||||||
|       ); |       ); | ||||||
|       this.articles = response.data; |       this.articles = response.data; | ||||||
|       if (this.articles.length > 0) { |       if (this.articles.length > 0) { | ||||||
|         this.selectArticle(this.articles[0].id); |         this.selectArticle(this.articles[0].id); | ||||||
|  |         this.resetArticleInactivityTimer(); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     async fetchSights() { |     async fetchSights() { | ||||||
|       const token = this.getCookie("auth_token"); |       const geoRes = await axios.get(`${GEO_URL}/v1/geolocation/context`); | ||||||
|       const geoRes = await axios.get( |  | ||||||
|         "http://31.129.106.67:6001/v1/geolocation/context" |  | ||||||
|       ); |  | ||||||
|       const routeId = geoRes.data.routeId; |       const routeId = geoRes.data.routeId; | ||||||
|       if (!routeId) { |       if (!routeId) { | ||||||
|         console.warn("Missing routeId in geo context:", geoRes.data); |         console.warn("Missing routeId in geo context:", geoRes.data); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       const sightsRes = await axios.get( |       const sightsRes = await axios.get(`${API_URL}/route/${routeId}/sight`); | ||||||
|         `https://wn-ts.krbl.ru/route/${routeId}/sight`, |  | ||||||
|         { |  | ||||||
|           headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|         } |  | ||||||
|       ); |  | ||||||
|       const rawSights = sightsRes.data; |       const rawSights = sightsRes.data; | ||||||
|       const detailedSights = await Promise.all( |       const detailedSights = await Promise.all( | ||||||
|         rawSights.map(async (sight) => { |         rawSights.map(async (sight) => { | ||||||
|           const detailRes = await axios.get( |           const detailRes = await axios.get(`${API_URL}/sight/${sight.id}`); | ||||||
|             `https://wn-ts.krbl.ru/sight/${sight.id}`, |           const thumbnailUrl = detailRes.data.thumbnail | ||||||
|             { |             ? await this.getMediaBlobUrl(detailRes.data.thumbnail) | ||||||
|               headers: { Authorization: `Bearer ${token}` }, |             : ""; | ||||||
|             } |  | ||||||
|           ); |  | ||||||
|           return { |           return { | ||||||
|             id: sight.id, |             id: sight.id, | ||||||
|             name: detailRes.data.name, |             name: detailRes.data.name, | ||||||
|             thumbnail: detailRes.data.thumbnail, |             thumbnailUrl, | ||||||
|           }; |           }; | ||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
| @@ -376,27 +326,63 @@ export default { | |||||||
|     }, |     }, | ||||||
|     toggleSightsList() { |     toggleSightsList() { | ||||||
|       this.showSightsList = !this.showSightsList; |       this.showSightsList = !this.showSightsList; | ||||||
|  |       if (this.showSightsList) { | ||||||
|  |         this.resetSightsInactivityTimer(); | ||||||
|  |       } else if (this.sightsInactivityTimer) { | ||||||
|  |         clearTimeout(this.sightsInactivityTimer); | ||||||
|  |         this.sightsInactivityTimer = null; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     resetSightsInactivityTimer() { | ||||||
|  |       if (this.articleInactivityTimer) { | ||||||
|  |         clearTimeout(this.articleInactivityTimer); | ||||||
|  |       } | ||||||
|  |       if (this.sightsInactivityTimer) { | ||||||
|  |         clearTimeout(this.sightsInactivityTimer); | ||||||
|  |       } | ||||||
|  |       if (this.showSightsList) { | ||||||
|  |         this.sightsInactivityTimer = setTimeout(() => { | ||||||
|  |           this.showSightsList = false; | ||||||
|  |           this.sightsInactivityTimer = null; | ||||||
|  |         }, 300000); // 5 m | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     resetArticleInactivityTimer() { | ||||||
|  |       if (this.articleInactivityTimer) | ||||||
|  |         clearTimeout(this.articleInactivityTimer); | ||||||
|  |  | ||||||
|  |       this.articleInactivityTimer = setTimeout(() => { | ||||||
|  |         if ( | ||||||
|  |           this.articles.length > 0 && | ||||||
|  |           this.selectedArticleId !== this.articles[0].id | ||||||
|  |         ) { | ||||||
|  |           this.selectArticle(this.articles[0].id); | ||||||
|  |         } | ||||||
|  |       }, 300_000); // 5 m | ||||||
|  |     }, | ||||||
|  |     handleUserActivity() { | ||||||
|  |       if (this.showSightsList) this.resetSightsInactivityTimer(); | ||||||
|  |       this.resetArticleInactivityTimer(); | ||||||
|     }, |     }, | ||||||
|     selectArticle(id) { |     selectArticle(id) { | ||||||
|  |       this.resetArticleInactivityTimer(); | ||||||
|       this.selectedArticleId = id; |       this.selectedArticleId = id; | ||||||
|       const selected = this.articles.find((article) => article.id === id); |       const selected = this.articles.find((article) => article.id === id); | ||||||
|       this.selectedArticleBody = selected ? selected.body : ""; |       this.selectedArticleBody = selected ? selected.body : ""; | ||||||
|       this.fetchArticleMedia(id); |       this.fetchArticleMedia(id); | ||||||
|     }, |     }, | ||||||
|     async fetchArticleMedia(articleId) { |     async fetchArticleMedia(articleId) { | ||||||
|       const token = this.getCookie("auth_token"); |  | ||||||
|       try { |       try { | ||||||
|         const response = await axios.get( |         const response = await axios.get( | ||||||
|           `https://wn-ts.krbl.ru/article/${articleId}/media`, |           `${API_URL}/article/${articleId}/media` | ||||||
|           { |  | ||||||
|             headers: { |  | ||||||
|               Authorization: `Bearer ${token}`, |  | ||||||
|             }, |  | ||||||
|           } |  | ||||||
|         ); |         ); | ||||||
|         if (response.data && response.data.length > 0) { |         if (response.data && response.data.length > 0) { | ||||||
|           const mediaId = response.data[0].id; |           const mediaId = response.data[0].id; | ||||||
|           this.imageUrl = `http://31.129.106.67:8080/media/${mediaId}/download`; |           try { | ||||||
|  |             this.imageUrl = await this.getMediaBlobUrl(mediaId); | ||||||
|  |           } catch { | ||||||
|  |             this.imageUrl = `${API_URL}/media/${mediaId}/download`; | ||||||
|  |           } | ||||||
|         } else { |         } else { | ||||||
|           this.imageUrl = ""; |           this.imageUrl = ""; | ||||||
|         } |         } | ||||||
| @@ -405,39 +391,60 @@ export default { | |||||||
|         this.imageUrl = ""; |         this.imageUrl = ""; | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     async fetchGeolocationContext() { |  | ||||||
|  |     async getMediaBlobUrl(mediaId) { | ||||||
|       try { |       try { | ||||||
|         const response = await axios.get( |         const response = await axios.get( | ||||||
|           "http://31.129.106.67:6001/v1/geolocation/context" |           `${API_URL}/media/${mediaId}/download`, | ||||||
|         ); |  | ||||||
|         this.routeProgress = response.data.routeProgress; |  | ||||||
|         console.log("Updated routeProgress:", this.routeProgress); |  | ||||||
|         const token = this.getCookie("auth_token"); |  | ||||||
|         const stopsResponse = await axios.get( |  | ||||||
|           `https://wn-ts.krbl.ru/route/${response.data.routeId}/station`, |  | ||||||
|           { |           { | ||||||
|             headers: { Authorization: `Bearer ${token}` }, |             responseType: "blob", | ||||||
|           } |           } | ||||||
|         ); |         ); | ||||||
|  |         return URL.createObjectURL(response.data); | ||||||
|  |       } catch (error) { | ||||||
|  |         console.error( | ||||||
|  |           `Failed to download media ${mediaId}:`, | ||||||
|  |           error?.response?.status, | ||||||
|  |           error?.response?.data || error.message | ||||||
|  |         ); | ||||||
|  |         return `${API_URL}/media/${mediaId}/download`; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     async fetchGeolocationContext() { | ||||||
|  |       try { | ||||||
|  |         const response = await axios.get(`${GEO_URL}/v1/geolocation/context`); | ||||||
|  |         this.routeProgress = response.data.routeProgress; | ||||||
|  |         const stopsResponse = await axios.get( | ||||||
|  |           `${API_URL}/route/${response.data.routeId}/station` | ||||||
|  |         ); | ||||||
|         this.stops = stopsResponse.data; |         this.stops = stopsResponse.data; | ||||||
|         const newSightId = response.data.nearestSightId; |         let newSightId = response.data.nearestSightId; | ||||||
|  |  | ||||||
|  |         if (!newSightId) { | ||||||
|  |           if (!this.sights || this.sights.length === 0) { | ||||||
|  |             await this.fetchSights(); | ||||||
|  |           } | ||||||
|  |           if (this.sights && this.sights.length > 0) { | ||||||
|  |             newSightId = this.sights[1].id; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         if (newSightId && newSightId !== this.sightId) { |         if (newSightId && newSightId !== this.sightId) { | ||||||
|           this.sightId = newSightId; |           this.sightId = newSightId; | ||||||
|           await this.fetchSightInfo(); |           await this.fetchSightInfo(); | ||||||
|           await this.fetchArticles(); |           await this.fetchArticles(); | ||||||
|         } |         } | ||||||
|         const nextStopId = response.data.routeProgress?.endStopId; |         const nextStopId = response.data.routeProgress?.endStopId; | ||||||
|         console.log("Fetched next stop ID:", nextStopId); |         // console.log("Fetched next stop ID:", nextStopId); | ||||||
|         console.log("Stops:", this.stops); |         // console.log("Stops:", this.stops); | ||||||
|         if (nextStopId && this.stops) { |         if (nextStopId && this.stops) { | ||||||
|           const nextStop = this.stops.find((stop) => stop.id == nextStopId); |           const nextStop = this.stops.find((stop) => stop.id == nextStopId); | ||||||
|           console.log("Fetched next stop ID:", nextStopId); |           // console.log("Fetched next stop ID:", nextStopId); | ||||||
|           console.log("Matching stop:", nextStop); |           // console.log("Matching stop:", nextStop); | ||||||
|           if (nextStop && nextStop.transfers) { |           if (nextStop && nextStop.transfers) { | ||||||
|             console.log("Transfers at next stop:", nextStop.transfers); |             // console.log("Transfers at next stop:", nextStop.transfers); | ||||||
|             this.nextStopTransfers = nextStop.transfers; |             this.nextStopTransfers = nextStop.transfers; | ||||||
|           } else { |           } else { | ||||||
|             console.log("No transfers found at next stop"); |             // console.log("No transfers found at next stop"); | ||||||
|             this.nextStopTransfers = null; |             this.nextStopTransfers = null; | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
| @@ -456,7 +463,7 @@ export default { | |||||||
|     toggleTransfers() { |     toggleTransfers() { | ||||||
|       console.log("Transfer toggle clicked"); |       console.log("Transfer toggle clicked"); | ||||||
|       this.showTransfers = !this.showTransfers; |       this.showTransfers = !this.showTransfers; | ||||||
|       console.log("showTransfers:", this.showTransfers); |       // console.log("showTransfers:", this.showTransfers); | ||||||
|  |  | ||||||
|       if (!this.showTransfers) { |       if (!this.showTransfers) { | ||||||
|         return; |         return; | ||||||
| @@ -468,32 +475,46 @@ export default { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       const nextStopId = this.routeProgress.endStopId; |       const nextStopId = this.routeProgress.endStopId; | ||||||
|       console.log("Next stop ID from routeProgress:", nextStopId); |       // console.log("Next stop ID from routeProgress:", nextStopId); | ||||||
|       console.log("All stops:", this.stops); |       // console.log("All stops:", this.stops); | ||||||
|  |  | ||||||
|       const nextStop = this.stops.find((stop) => stop.id === nextStopId); |       const nextStop = this.stops.find((stop) => stop.id === nextStopId); | ||||||
|       console.log("Next stop object:", nextStop); |       // console.log("Next stop object:", nextStop); | ||||||
|  |  | ||||||
|       if (nextStop && nextStop.transfers) { |       if (nextStop && nextStop.transfers) { | ||||||
|         console.log("Transfers at next stop:", nextStop.transfers); |         // console.log("Transfers at next stop:", nextStop.transfers); | ||||||
|         this.nextStopTransfers = nextStop.transfers; |         this.nextStopTransfers = nextStop.transfers; | ||||||
|       } else { |       } else { | ||||||
|         console.log("No transfers found at next stop"); |         // console.log("No transfers found at next stop"); | ||||||
|         this.nextStopTransfers = null; |         this.nextStopTransfers = null; | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   async mounted() { |   async mounted() { | ||||||
|     await this.checkAuth(); |  | ||||||
|     await this.fetchGeolocationContext(); |  | ||||||
|     await this.fetchSights(); |     await this.fetchSights(); | ||||||
|  |     await this.fetchGeolocationContext(); | ||||||
|     this.geolocationInterval = setInterval(() => { |     this.geolocationInterval = setInterval(() => { | ||||||
|       this.fetchGeolocationContext(); |       this.fetchGeolocationContext(); | ||||||
|     }, 1000); |     }, 1000); | ||||||
|  |     window.addEventListener("mousemove", this.handleUserActivity); | ||||||
|  |     window.addEventListener("touchstart", this.handleUserActivity, { | ||||||
|  |       passive: true, | ||||||
|  |     }); | ||||||
|  |     window.addEventListener("keydown", this.handleUserActivity); | ||||||
|  |     window.addEventListener("click", this.handleUserActivity, true); | ||||||
|     // this.fetchSightInfo(); |     // this.fetchSightInfo(); | ||||||
|     // this.fetchArticles(); |     // this.fetchArticles(); | ||||||
|   }, |   }, | ||||||
|   unmounted() { |   unmounted() { | ||||||
|  |     if (this.sightsInactivityTimer) { | ||||||
|  |       clearTimeout(this.sightsInactivityTimer); | ||||||
|  |     } | ||||||
|  |     window.removeEventListener("mousemove", this.handleUserActivity); | ||||||
|  |     window.removeEventListener("touchstart", this.handleUserActivity, { | ||||||
|  |       passive: true, | ||||||
|  |     }); | ||||||
|  |     window.removeEventListener("keydown", this.handleUserActivity); | ||||||
|  |     window.removeEventListener("click", this.handleUserActivity, true); | ||||||
|     if (this.autoCloseTimer) { |     if (this.autoCloseTimer) { | ||||||
|       clearTimeout(this.autoCloseTimer); |       clearTimeout(this.autoCloseTimer); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="weatherinfo"> |   <div class="weatherinfo" :class="{ hidden: isPresidentAddressOpen }"> | ||||||
|     <div class="time">{{ formattedTime }}</div> |     <div class="time">{{ formattedTime }}</div> | ||||||
|     <div class="date">{{ formattedDate }}, {{ dayOfWeek }}</div> |     <div class="date">{{ formattedDate }}, {{ dayOfWeek }}</div> | ||||||
|  |  | ||||||
| @@ -10,7 +10,7 @@ | |||||||
|           alt="Weather Icon" |           alt="Weather Icon" | ||||||
|         /> |         /> | ||||||
|         <div class="temperature-celsius"> |         <div class="temperature-celsius"> | ||||||
|           {{ currentWeather.temperatureCelsius }}°C |           {{ currentWeather.temperatureCelsius }}° | ||||||
|         </div> |         </div> | ||||||
|         <div> |         <div> | ||||||
|           {{ currentWeather.description }} |           {{ currentWeather.description }} | ||||||
| @@ -27,8 +27,8 @@ | |||||||
|           <div class="humidity"> |           <div class="humidity"> | ||||||
|             <svg |             <svg | ||||||
|               xmlns="http://www.w3.org/2000/svg" |               xmlns="http://www.w3.org/2000/svg" | ||||||
|               width="12" |               width="13" | ||||||
|               height="16" |               height="18" | ||||||
|               fill="none" |               fill="none" | ||||||
|               viewBox="0 0 12 16" |               viewBox="0 0 12 16" | ||||||
|             > |             > | ||||||
| @@ -43,8 +43,8 @@ | |||||||
|           <div class="wind"> |           <div class="wind"> | ||||||
|             <svg |             <svg | ||||||
|               xmlns="http://www.w3.org/2000/svg" |               xmlns="http://www.w3.org/2000/svg" | ||||||
|               width="16" |               width="18" | ||||||
|               height="16" |               height="18" | ||||||
|               fill="none" |               fill="none" | ||||||
|               viewBox="0 0 16 16" |               viewBox="0 0 16 16" | ||||||
|             > |             > | ||||||
| @@ -72,8 +72,9 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import "../assets/style/main.css"; | import "../assets/style/main.css"; | ||||||
|  | import { API_URL } from "../config"; | ||||||
|  |  | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
| import Cookies from "js-cookie"; |  | ||||||
| import clearIcon from "@/icons/clear-day.svg"; | import clearIcon from "@/icons/clear-day.svg"; | ||||||
| import cloudsIcon from "@/icons/cloudy.svg"; | import cloudsIcon from "@/icons/cloudy.svg"; | ||||||
| import rainIcon from "@/icons/rainy.svg"; | import rainIcon from "@/icons/rainy.svg"; | ||||||
| @@ -84,6 +85,12 @@ import fogIcon from "@/icons/fog.svg"; | |||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: "WeatherInfo", |   name: "WeatherInfo", | ||||||
|  |   props: { | ||||||
|  |     isPresidentAddressOpen: { | ||||||
|  |       type: Boolean, | ||||||
|  |       default: false, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       isModalOpen: false, |       isModalOpen: false, | ||||||
| @@ -101,55 +108,24 @@ export default { | |||||||
|       dayOfWeek: "", |       dayOfWeek: "", | ||||||
|       timerInterval: null, |       timerInterval: null, | ||||||
|       weatherDescriptionMap: { |       weatherDescriptionMap: { | ||||||
|         thunderstorm: "Гроза", |         thunderstorm: "гроза", | ||||||
|         drizzle: "Слабый дождь", |         drizzle: "слабый дождь", | ||||||
|         rain: "Дождь", |         rain: "дождь", | ||||||
|         snow: "Снег", |         snow: "снег", | ||||||
|         atmosphere: "Туман", |         atmosphere: "туман", | ||||||
|         clear: "Ясно", |         clear: "ясно", | ||||||
|         clouds: "Облачно", |         clouds: "облачно", | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     async getToken() { |  | ||||||
|       let token = Cookies.get("auth_token"); |  | ||||||
|       try { |  | ||||||
|         await axios.get("https://wn-ts.krbl.ru/auth/me", { |  | ||||||
|           headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|         }); |  | ||||||
|       } catch (error) { |  | ||||||
|         if (error.response && error.response.status === 401) { |  | ||||||
|           const response = await axios.post( |  | ||||||
|             "https://wn-ts.krbl.ru/auth/login", |  | ||||||
|             { |  | ||||||
|               email: "admin", |  | ||||||
|               password: "changeme", |  | ||||||
|             } |  | ||||||
|           ); |  | ||||||
|           token = response.data.token; |  | ||||||
|           Cookies.set("auth_token", token); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       return token; |  | ||||||
|     }, |  | ||||||
|     async fetchSightInfo() { |     async fetchSightInfo() { | ||||||
|       const token = await this.getToken(); |       const response = await axios.get(`${API_URL}/sight/${this.sightId}`); | ||||||
|       const response = await axios.get( |  | ||||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}`, |  | ||||||
|         { |  | ||||||
|           headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|         } |  | ||||||
|       ); |  | ||||||
|       this.stopName = response.data.name; |       this.stopName = response.data.name; | ||||||
|     }, |     }, | ||||||
|     async fetchArticles() { |     async fetchArticles() { | ||||||
|       const token = await this.getToken(); |  | ||||||
|       const response = await axios.get( |       const response = await axios.get( | ||||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}/article`, |         `${API_URL}/sight/${this.sightId}/article` | ||||||
|         { |  | ||||||
|           headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|         } |  | ||||||
|       ); |       ); | ||||||
|       this.articles = response.data; |       this.articles = response.data; | ||||||
|       if (this.articles.length > 0) { |       if (this.articles.length > 0) { | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								src/config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | export const API_URL = process.env.VUE_APP_API_URL; | ||||||
|  | export const GEO_URL = process.env.VUE_APP_GEO_URL; | ||||||
							
								
								
									
										35
									
								
								src/icons/tram-bottom-left.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-bottom-left.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										35
									
								
								src/icons/tram-bottom-right.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-bottom-right.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										35
									
								
								src/icons/tram-left.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-left.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										35
									
								
								src/icons/tram-right.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-right.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										35
									
								
								src/icons/tram-top-left.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-top-left.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										35
									
								
								src/icons/tram-top-right.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-top-right.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
		Reference in New Issue
	
	Block a user