From 7839deb59278b24cce9bb6887c9ddc1a771abd40 Mon Sep 17 00:00:00 2001 From: Sergey Akkuratov Date: Thu, 30 May 2024 18:06:37 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20sa-router-spa=20=D0=B8=D0=B7=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D1=8B=D0=B4=D1=83=D1=89=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .husky/_/husky.sh | 31 +++++++++++++++++ package-lock.json | 29 ++++++++++------ package.json | 7 ++-- src/localStorage.js | 4 +-- src/pageTempaltes.js | 42 ++++++++++++++++++++++ src/style.css | 11 ++++++ src/weatherApp.js | 83 +++++++++++++++++++++++++------------------- webpack.config.js | 11 +++++- 8 files changed, 168 insertions(+), 50 deletions(-) create mode 100644 .husky/_/husky.sh create mode 100644 src/pageTempaltes.js diff --git a/.husky/_/husky.sh b/.husky/_/husky.sh new file mode 100644 index 0000000..6809ccc --- /dev/null +++ b/.husky/_/husky.sh @@ -0,0 +1,31 @@ +#!/bin/sh +if [ -z "$husky_skip_init" ]; then + debug () { + if [ "$HUSKY_DEBUG" = "1" ]; then + echo "husky (debug) - $1" + fi + } + + readonly hook_name="$(basename "$0")" + debug "starting $hook_name..." + + if [ "$HUSKY" = "0" ]; then + debug "HUSKY env variable is set to 0, skipping hook" + exit 0 + fi + + if [ -f ~/.huskyrc ]; then + debug "sourcing ~/.huskyrc" + . ~/.huskyrc + fi + + export readonly husky_skip_init=1 + sh -e "$0" "$@" + exitCode="$?" + + if [ $exitCode != 0 ]; then + echo "husky - $hook_name hook exited with code $exitCode (error)" + fi + + exit $exitCode +fi diff --git a/package-lock.json b/package-lock.json index 02b80fc..46c11df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "otus_homework_lesson07", "version": "1.0.0", "license": "ISC", + "dependencies": { + "sa-router-spa": "^1.0.1" + }, "devDependencies": { "@babel/preset-env": "^7.24.0", "css-loader": "^6.10.0", @@ -4380,9 +4383,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, "engines": { "node": ">= 0.6" @@ -5701,9 +5704,9 @@ } }, "node_modules/express": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", - "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", @@ -5711,7 +5714,7 @@ "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -9624,6 +9627,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sa-router-spa": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sa-router-spa/-/sa-router-spa-1.0.1.tgz", + "integrity": "sha512-WALl33tVy0RuTQ2qJTAJCXQ3eBPaNzQcWCyc3k9pRz+pt9piDMpwbizwoBvjk3aoenaVuF0K47xhyjygsbtDrg==" + }, "node_modules/safe-array-concat": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", @@ -11050,14 +11058,15 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.0.0.tgz", - "integrity": "sha512-tZ5hqsWwww/8DislmrzXE3x+4f+v10H1z57mA2dWFrILb4i3xX+dPhTkcdR0DLyQztrhF2AUmO5nN085UYjd/Q==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz", + "integrity": "sha512-hRLz+jPQXo999Nx9fXVdKlg/aehsw1ajA9skAneGmT03xwmyuhvF93p6HUKKbWhXdcERtGTzUCtIQr+2IQegrA==", "dev": true, "dependencies": { "colorette": "^2.0.10", "memfs": "^4.6.0", "mime-types": "^2.1.31", + "on-finished": "^2.4.1", "range-parser": "^1.2.1", "schema-utils": "^4.0.0" }, diff --git a/package.json b/package.json index 5315ce8..9f0890b 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "test": "jest --passWithNoTests --coverage", "lint": "prettier --check . && eslint .", "lint:fix": "prettier --write . && eslint . --fix", - "build": "webpack --mode=development", - "dev": "webpack serve --open --mode=development" + "build": "webpack --mode=production", + "start": "webpack serve --open --mode=development" }, "repository": { "type": "git", @@ -42,5 +42,8 @@ "lint-staged": { "*.js": "eslint --fix", "*.{js,css,md,yml}": "prettier --write" + }, + "dependencies": { + "sa-router-spa": "^1.0.1" } } diff --git a/src/localStorage.js b/src/localStorage.js index 2d5e4da..01c02bb 100644 --- a/src/localStorage.js +++ b/src/localStorage.js @@ -1,11 +1,11 @@ export function getHistoryList() { const historyString = localStorage.getItem("history"); if (historyString !== null && historyString.length > 0) { - return historyString.split(" "); + return historyString.split(";"); } return []; } export function setHistorySet(list) { - localStorage.setItem("history", list.join(" ")); + localStorage.setItem("history", list.join(";")); } diff --git a/src/pageTempaltes.js b/src/pageTempaltes.js new file mode 100644 index 0000000..351a8ab --- /dev/null +++ b/src/pageTempaltes.js @@ -0,0 +1,42 @@ +import oopsImg from "./assets/oops.png"; + +export const mainTemplate = ` +
+
+
+ +
+
+ Couldn't get image of map +
+
+ Wait for city name +
+
+
+ History: +
+
+
+ About +
+`; + +export const aboutTemplate = ` +
+

Приложение "Прогноз погоды"

+

+ Выполнение домашнего задания для лекции "Современный инструментарий при разработке клиентских (и не только + приложений)" +

+ +

О проекте

+

Приложение "Прогноз погоды" это Web приложение разработанное на языке JavaScript. Оно предназначено для + просмотра текущей погоды в конкретном городе.

+

Приложение показывает текущую температуру в градусах цельсия и общее состоянии погоды, обозначенное иконкой.

+

Так же приложение отображает компактную картинку с картой выбранного города.

+Main +
+`; diff --git a/src/style.css b/src/style.css index 25c11df..79b5b98 100644 --- a/src/style.css +++ b/src/style.css @@ -64,3 +64,14 @@ color: red; cursor: pointer; } + +.bottom { + clear: both; + position: relative; +} + +.bottom a { + position: absolute; + top: 50%; + left: 50%; +} diff --git a/src/weatherApp.js b/src/weatherApp.js index 099e106..1d5e5da 100644 --- a/src/weatherApp.js +++ b/src/weatherApp.js @@ -2,16 +2,19 @@ /* eslint-disable no-alert */ /* eslint no-param-reassign: ["error", { "props": false }] */ // eslint-disable-next-line import/no-self-import +import Router from "sa-router-spa"; import oopsImg from "./assets/oops.png"; import { getInfoByIP, getMap, getWeather } from "./externalRequests"; import { getHistoryList, setHistorySet } from "./localStorage"; +import { mainTemplate, aboutTemplate } from "./pageTempaltes"; -export default async function weatherApp(element) { +export default async function weatherApp(rootElement) { const maxHistorylines = 10; const historyList = []; + const router = new Router("history"); function showWeather(data) { - const weatherInfo = element.querySelector("#info"); + const weatherInfo = rootElement.querySelector("#info"); weatherInfo.innerHTML = `

${data.name}

`; weatherInfo.innerHTML += `

Temperature: ${data.main.temp} C

`; weatherInfo.innerHTML += `Couldn't load icon of weather`; @@ -19,13 +22,14 @@ export default async function weatherApp(element) { function showMap(imgSource) { if (imgSource) { - element.querySelector("#map").src = URL.createObjectURL(imgSource); + rootElement.querySelector("#map").src = URL.createObjectURL(imgSource); } else { - element.querySelector("#map").src = oopsImg; + rootElement.querySelector("#map").src = oopsImg; } } - async function updateWeather(cityName, updateHistoryFlag) { + async function updateWeather(cityNameParam, updateHistoryFlag) { + const cityName = decodeURIComponent(cityNameParam); const weather = await getWeather(cityName); if (weather.cod === 200) { if (updateHistoryFlag) await updateHistoryBlock(cityName); @@ -38,8 +42,7 @@ export default async function weatherApp(element) { } function onclickHistoryLine(event) { - updateWeather(event.target.innerHTML, false); - updateHistoryBlock(event.target.innerHTML); + router.navigate(`/weather/${event.target.innerHTML}`); } function createHistoryParagraph(cityName) { @@ -51,7 +54,7 @@ export default async function weatherApp(element) { } async function updateHistoryBlock(cityName) { - const historyElement = element.querySelector("#history"); + const historyElement = rootElement.querySelector("#history"); if (historyList.includes(cityName)) { historyList.splice(historyList.indexOf(cityName), 1); historyList.unshift(cityName); @@ -76,34 +79,11 @@ export default async function weatherApp(element) { const ipInfo = await getInfoByIP(); - element.innerHTML = ` -
-
- -
-
- Couldn't get image of map -
-
- Wait for city name -
-
-
- History: -
`; - const localHistoryList = getHistoryList(); - if (localHistoryList.length > 0) { - localHistoryList - .reverse() - .forEach((cityName) => updateHistoryBlock(cityName)); - } - if (ipInfo.region) updateWeather(ipInfo.region); + rootElement.innerHTML = mainTemplate; - element + rootElement .querySelector("#weatherForm") - .addEventListener("submit", async (event) => { + .addEventListener("submit", (event) => { event.preventDefault(); // читаем значение из формы @@ -111,6 +91,39 @@ export default async function weatherApp(element) { const inputEl = formElement.querySelector("#userInput"); const cityName = inputEl.value; inputEl.value = ""; - updateWeather(cityName, true); + router.navigate(`/weather/${cityName}`); }); + + rootElement.querySelectorAll("a").forEach((link) => + link.addEventListener("click", (event) => { + event.preventDefault(); + router.navigate(link.href); + }), + ); + + router.addRoute({ + path: /^\/weather\/(?.+)$/, + onEnter: async (params) => { + updateWeather(params.cityName, true); + }, + }); + + router.addRoute({ + path: "/about", + onEnter: () => { + rootElement.innerHTML = aboutTemplate; + }, + onLeave: () => { + rootElement.innerHTML = mainTemplate; + }, + }); + + const localHistoryList = getHistoryList(); + if (localHistoryList.length > 0) { + localHistoryList + .reverse() + .forEach((cityName) => updateHistoryBlock(cityName)); + } + + if (ipInfo.city) router.navigate(`/weather/${ipInfo.city}`); } diff --git a/webpack.config.js b/webpack.config.js index 0c719dd..1a457be 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,6 +7,7 @@ module.exports = { filename: "[name].js", chunkFilename: "[name].chunk.js", path: path.resolve(__dirname, "dist"), + publicPath: "/", }, plugins: [ new HtmlWebpackPlugin({ @@ -14,6 +15,11 @@ module.exports = { favicon: "./src/assets/favicon.png", filename: "./index.html", }), + new HtmlWebpackPlugin({ + template: "./src/template.html", + favicon: "./src/assets/favicon.png", + filename: "404.html", + }), ], module: { rules: [ @@ -28,7 +34,10 @@ module.exports = { ], }, devServer: { - static: "./dist", + compress: true, + port: 9000, + watchFiles: ["dist/index.html"], + historyApiFallback: true, }, optimization: { runtimeChunk: "single",