diff --git a/package-lock.json b/package-lock.json index 5bec6f3ed..5681d5e3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "apexcharts": "^4.1.0", "autoprefixer": "^10.4.20", "awesomplete": "^1.1.5", + "axios": "^1.7.9", "bootstrap-cookie-alert": "^1.2.1", "chart.js": "^2.9.4", "croppie": "^2.6.5", @@ -1845,6 +1846,12 @@ "dev": true, "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -1888,6 +1895,17 @@ "integrity": "sha512-hwClzFReIIfheoMWpoJ7juLp1o6Q7YjTtEdZIGfXvhRHA9ewKM03VS4ZoK+OEFIKM066PfQt9pzRIGV1TguRBw==", "license": "MIT" }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2129,6 +2147,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2285,6 +2315,15 @@ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/destr": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", @@ -2499,6 +2538,26 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -2516,6 +2575,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3046,6 +3119,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -3624,6 +3718,12 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", diff --git a/package.json b/package.json index 104b1d797..422109490 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "apexcharts": "^4.1.0", "autoprefixer": "^10.4.20", "awesomplete": "^1.1.5", + "axios": "^1.7.9", "bootstrap-cookie-alert": "^1.2.1", "chart.js": "^2.9.4", "croppie": "^2.6.5", diff --git a/resources/vue/components/StationAutocomplete/StationAutocomplete.vue b/resources/vue/components/StationAutocomplete/StationAutocomplete.vue index db906ce56..a67b9b03c 100644 --- a/resources/vue/components/StationAutocomplete/StationAutocomplete.vue +++ b/resources/vue/components/StationAutocomplete/StationAutocomplete.vue @@ -11,391 +11,415 @@ import Spinner from "../Spinner.vue"; import LineIndicator from "../LineIndicator.vue"; import ActiveStatusCard from "../ActiveStatusCard.vue"; import FriendDropdown from "../Helpers/FriendDropdown.vue"; +import axios from "axios"; export default { - setup() { - const userStore = useUserStore(); - userStore.fetchSettings(); - return {userStore}; + setup() { + const userStore = useUserStore(); + userStore.fetchSettings(); + return {userStore}; + }, + name: "StationAutocomplete", + emits: ["update:station", "update:time", "update:travelType"], + components: { + ActiveStatusCard, + LineIndicator, + Spinner, + AutocompleteListEntry, + FullScreenModal, + VueDatePicker, + FriendDropdown + }, + props: { + station: { + type: Object, + required: false, + default: null, }, - name: "StationAutocomplete", - emits: ["update:station", "update:time", "update:travelType"], - components: { - ActiveStatusCard, - LineIndicator, - Spinner, - AutocompleteListEntry, - FullScreenModal, - VueDatePicker, - FriendDropdown + stationName: { + type: String, + required: false, + default: null, }, - props: { - station: { - type: Object, - required: false, - default: null, - }, - stationName: { - type: String, - required: false, - default: null, - }, - dashboard: { - type: Boolean, - required: false, - default: false - }, - time: { - type: DateTime, - required: false, - default: null - }, - showFilterButton: { - type: Boolean, - required: false, - default: false - }, - showGpsButton: { - type: Boolean, - required: false, - default: false - } + dashboard: { + type: Boolean, + required: false, + default: false }, - data() { - return { - recent: [], - loading: false, - autocompleteList: [], - stationInput: "", - showFilter: false, - date: null, - selectedStation: null, - selectedType: null, - fetchingGps: false, - fetchingTextInput: false, - travelTypes: [ - {value: "express", color: "rgba(197,199,196,0.5)", icon: "fa-train", contrast: true}, - {value: "regional", color: "rgba(193,18,28,0.5)", icon: "fa-train"}, - {value: "suburban", color: "rgba(0,111,53,0.5)", icon: "fa-train", image: "/img/suburban.svg"}, - {value: "subway", color: "rgba(21,106,184,0.5)", icon: "fa-subway", image: "/img/subway.svg"}, - {value: "tram", color: "rgba(217,34,42,0.5)", icon: "fa-tram", image: "/img/tram.svg"}, - {value: "bus", color: "rgba(163,0,124,0.5)", icon: "fa-bus", image: "/img/bus.svg"}, - {value: "ferry", color: "rgba(21,106,184,0.5)", icon: "fa-ship"}, - {value: "taxi", color: "rgb(255,237,74,0.5)", icon: "fa-taxi", contrast: true}, - ] - }; + time: { + type: DateTime, + required: false, + default: null }, - methods: { - trans, - showModal() { - this.$refs.modal.show(); - }, - setHome() { - if (!this.isHome) { - this.userStore.setHome(this.station).catch((error) => { - window.notyf.error(trans('action.error') + " (" + trans('action.set-home') + ")"); - }) - } - }, - getRecent() { - fetch(`/api/v1/trains/station/history`).then((response) => { - response.json().then((result) => { - this.recent = result.data; - }); - }); - }, - autocomplete() { - this.loading = true; - if (!this.stationInput || this.stationInput.length < 2) { - this.autocompleteList = []; - this.loading = false; - return; - } + showFilterButton: { + type: Boolean, + required: false, + default: false + }, + showGpsButton: { + type: Boolean, + required: false, + default: false + } + }, + data() { + return { + recent: [], + loading: false, + lastError: null, + autocompleteList: [], + stationInput: "", + showFilter: false, + date: null, + selectedStation: null, + selectedType: null, + fetchingGps: false, + fetchingTextInput: false, + travelTypes: [ + {value: "express", color: "rgba(197,199,196,0.5)", icon: "fa-train", contrast: true}, + {value: "regional", color: "rgba(193,18,28,0.5)", icon: "fa-train"}, + {value: "suburban", color: "rgba(0,111,53,0.5)", icon: "fa-train", image: "/img/suburban.svg"}, + {value: "subway", color: "rgba(21,106,184,0.5)", icon: "fa-subway", image: "/img/subway.svg"}, + {value: "tram", color: "rgba(217,34,42,0.5)", icon: "fa-tram", image: "/img/tram.svg"}, + {value: "bus", color: "rgba(163,0,124,0.5)", icon: "fa-bus", image: "/img/bus.svg"}, + {value: "ferry", color: "rgba(21,106,184,0.5)", icon: "fa-ship"}, + {value: "taxi", color: "rgb(255,237,74,0.5)", icon: "fa-taxi", contrast: true}, + ] + }; + }, + methods: { + trans, + showModal() { + this.$refs.modal.show(); + }, + setHome() { + if (!this.isHome) { + this.userStore.setHome(this.station).catch((error) => { + window.notyf.error(trans('action.error') + " (" + trans('action.set-home') + ")"); + }) + } + }, + getRecent() { + fetch(`/api/v1/trains/station/history`).then((response) => { + response.json().then((result) => { + this.recent = result.data; + }); + }); + }, + autocomplete() { + this.loading = true; + if (!this.stationInput || this.stationInput.length < 2) { + this.autocompleteList = []; + this.loading = false; + return; + } - this.fetchAutocomplete().then((result) => { - this.autocompleteList = result.data; - this.loading = false; - }); - }, - async fetchAutocomplete() { - let query = this.stationInput.replace(/%2F/, " ").replace(/\//, " "); - const res = await fetch(`/api/v1/trains/station/autocomplete/${query}`); - return await res.json(); - }, - showPicker() { - this.$refs.picker.openMenu(); - }, - setTime() { - this.$emit("update:time", DateTime.fromJSDate(this.date).setZone('UTC').toISO()); - }, - setStationFromText() { - this.fetchingTextInput = true; - this.fetchAutocomplete().then((result) => { - this.fetchingTextInput = false; - this.setStation(result.data.shift()); - }).catch(() => { - this.fetchingTextInput = false; - }); - }, - setStation(item) { - this.stationInput = item.name; - this.selectedStation = item; - this.$emit("update:station", item); - this.$refs.modal.hide(); - const url = `/stationboard?stationId=${item.id}&stationName=${item.name}`; - if (this.$props.dashboard) { - window.location = url; - } - }, - setTravelType(travelType) { - this.selectedType = this.selectedType === travelType.value ? null : travelType.value; - this.$emit("update:travelType", this.selectedType); - }, - setStationFromGps() { - this.fetchingGps = true; - if (!navigator.geolocation) { - this.fetchingGps = false; - notyf.error(trans("stationboard.position-unavailable")); - return; - } + this.fetchAutocomplete() + .then((result) => { + this.autocompleteList = result.data; + this.loading = false; + }); + }, + async fetchAutocomplete() { + this.loading = true; + this.lastError = null; + let query = this.stationInput.replace(/%2F/, " ").replace(/\//, " "); + return await axios.get(`/api/v1/trains/station/autocomplete/${query}`) + .then((response) => { + this.autocompleteList = response.data; + return response.data; + }) + .catch((error) => { + this.lastError = error.message; + notyf.error(error.message); + }) + .finally(() => { + this.loading = false; + }); + }, + showPicker() { + this.$refs.picker.openMenu(); + }, + setTime() { + this.$emit("update:time", DateTime.fromJSDate(this.date).setZone('UTC').toISO()); + }, + setStationFromText() { + this.fetchingTextInput = true; + this.fetchAutocomplete() + .then((result) => { + this.fetchingTextInput = false; + this.setStation(result.data.shift()); + }) + .catch(() => { + this.fetchingTextInput = false; + }); + }, + setStation(item) { + this.stationInput = item.name; + this.selectedStation = item; + this.$emit("update:station", item); + this.$refs.modal.hide(); + const url = `/stationboard?stationId=${item.id}&stationName=${item.name}`; + if (this.$props.dashboard) { + window.location = url; + } + }, + setTravelType(travelType) { + this.selectedType = this.selectedType === travelType.value ? null : travelType.value; + this.$emit("update:travelType", this.selectedType); + }, + setStationFromGps() { + this.fetchingGps = true; + if (!navigator.geolocation) { + this.fetchingGps = false; + notyf.error(trans("stationboard.position-unavailable")); + return; + } - navigator.geolocation.getCurrentPosition( - (position) => { - fetch(`/api/v1/trains/station/nearby?latitude=${position.coords.latitude}&longitude=${position.coords.longitude}`) - .then((data) => { - if (!data.ok) { - notyf.error(trans("stationboard.position-unavailable")); - this.fetchingGps = false; - } - data.json().then((result) => { - this.setStation(result.data); - this.fetchingGps = false; - }); - }) - }, - () => { - this.fetchingGps = false; + navigator.geolocation.getCurrentPosition( + (position) => { + fetch(`/api/v1/trains/station/nearby?latitude=${position.coords.latitude}&longitude=${position.coords.longitude}`) + .then((data) => { + if (!data.ok) { notyf.error(trans("stationboard.position-unavailable")); - } - ); - }, - clearInput() { - this.stationInput = ""; - this.$refs.stationInput.focus(); - } + this.fetchingGps = false; + } + data.json().then((result) => { + this.setStation(result.data); + this.fetchingGps = false; + }); + }) + }, + () => { + this.fetchingGps = false; + notyf.error(trans("stationboard.position-unavailable")); + } + ); }, - watch: { - stationInput: _.debounce(function () { - this.autocomplete(); - }, 500), - stationName() { - this.stationInput = this.stationName ? this.stationName : this.stationInput; - }, - station() { - this.selectedStation = this.station; - } + clearInput() { + this.stationInput = ""; + this.$refs.stationInput.focus(); + } + }, + watch: { + stationInput: _.debounce(function () { + this.autocomplete(); + }, 500), + stationName() { + this.stationInput = this.stationName ? this.stationName : this.stationInput; + }, + station() { + this.selectedStation = this.station; + } + }, + mounted() { + this.date = this.time; + this.stationInput = this.stationName ? this.stationName : ""; + this.selectedStation = this.station; + this.getRecent(); + }, + computed: { + placeholder() { + return `${trans('stationboard.station-placeholder')} ${trans('or-alternative')} ${trans('ril100')}`; }, - mounted() { - this.date = this.time; - this.stationInput = this.stationName ? this.stationName : ""; - this.selectedStation = this.station; - this.getRecent(); + dark() { + return localStorage.getItem('darkMode') === 'dark'; }, - computed: { - placeholder() { - return `${trans('stationboard.station-placeholder')} ${trans('or-alternative')} ${trans('ril100')}`; - }, - dark() { - return localStorage.getItem('darkMode') === 'dark'; - }, - isHome() { - return this.userStore.getHome && this.station && this.userStore.getHome.id === this.station.id; - } + isHome() { + return this.userStore.getHome && this.station && this.userStore.getHome.id === this.station.id; } + } }