From 62d59fd06f0d341a0c2d91530ca261606450a3fb Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Mon, 9 Dec 2024 02:04:22 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=B4=E2=80=8D=E2=98=A0=EF=B8=8F=20Start?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 28 ++++++ .gitignore | 2 + .prettierignore | 1 + .prettierrc.json | 10 ++ LICENSE | 24 +++++ README.md | 13 +++ package-lock.json | 27 ++++++ package.json | 5 + src/app.js | 233 ++++++++++++++++++++++++++++++++++++++++++++++ src/data.json | 19 ++++ src/index.html | 42 +++++++++ 11 files changed, 404 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 LICENSE create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/app.js create mode 100644 src/data.json create mode 100644 src/index.html diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a9cb61e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,28 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.js] +indent_style = space +indent_size = 2 + +[package.json] +indent_style = space +indent_size = 2 + +[{*.html,*.css,*.json}] +indent_style = tab +indent_size = 4 + +[humans.txt] +indent_style = tab +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1109f16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +src/bootstrap*.* +src/chart*.js diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..dd44972 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +*.md diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..681b10d --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,10 @@ +{ + "printWidth": 132, + "bracketSpacing": true, + "bracketSameLine": true, + "semi": true, + "singleQuote": true, + "arrowParens": "always", + "htmlWhitespaceSensitivity": "strict", + "endOfLine": "lf" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c52ad1 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +## Assets + +```text +https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.js +https://raw.githubusercontent.com/twbs/bootstrap/refs/tags/v5.3.3/dist/js/bootstrap.min.js +https://raw.githubusercontent.com/twbs/bootstrap/refs/tags/v5.3.3/dist/css/bootstrap.min.css +``` + +## TODO + +* [ ] favicon +* [ ] theme color +* [ ] deploy script diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6af0fec --- /dev/null +++ b/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "poppers-tracker", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "prettier": "3.4.1" + } + }, + "node_modules/prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4d52e07 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "prettier": "3.4.1" + } +} diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..b45f7e0 --- /dev/null +++ b/src/app.js @@ -0,0 +1,233 @@ +const data = { poppers: null, chart: null, default: { year: '2024', month: '12' } }; + +window.onload = async function () { + data.poppersData = await getData(); + + updateYears(data.poppersData); + + /* Автоматическиая загрузка последнего года и месяца */ + document.getElementById('yearSelect').value = data.default.year; + updateMonths(data.default.year); + document.getElementById('monthSelect').value = data.default.month; + showChart(data.default.month); + + document.getElementById('yearSelect').addEventListener('change', (self) => { + const value = self.target.value; + if (value === 'Выбрать') { + return; + } + + updateMonths(value); + }); + + document.getElementById('monthSelect').addEventListener('change', (self) => { + const value = self.target.value; + if (value === 'Выбрать') { + return; + } + + showChart(self.target.value); + }); +}; + +const getData = async () => { + const response = await fetch('data.json'); + + const posts = await response.json(); + + return posts; +}; + +function updateYears() { + 'use strict'; + + const years = document.getElementById('yearSelect'); + + for (const [key, value] of Object.entries(data.poppersData)) { + if (key === 'update') { + continue; + } + + const year = document.createElement('option'); + year.appendChild(document.createTextNode(key)); + year.setAttribute('value', key); + years.appendChild(year); + } +} + +function updateMonths(year) { + 'use strict'; + + const months = document.getElementById('monthSelect'); + + /* Очистка элемента */ + months.innerText = ''; + const month = document.createElement('option'); + month.appendChild(document.createTextNode('Выбрать')); + months.appendChild(month); + + for (const [key, value] of Object.entries(data.poppersData[year])) { + const month = document.createElement('option'); + month.appendChild(document.createTextNode(key)); + month.setAttribute('value', key); + months.appendChild(month); + } +} + +function showChart(selectedMonth) { + const year = document.getElementById('yearSelect').value; + const month = selectedMonth; + const ctx = document.getElementById('chart'); + + if (data.chart != null) { + data.chart.destroy(); + } + + const days = Object.keys(data.poppersData[year][month]); + const breathData = []; + for (const [key, value] of Object.entries(data.poppersData[year][month])) { + breathData.push(data.poppersData[year][month][key].length); + } + + data.chart = new Chart(ctx, { + type: 'line', + data: { + labels: days, + datasets: [ + { + label: ' Вдыханий', + data: breathData, + borderWidth: 2, + }, + ], + }, + options: { + responsive: true, + plugins: { + title: { + display: true, + text: `${month} месяц, ${year} год`, + }, + legend: { + position: 'bottom', + }, + tooltip: { + enabled: false, + position: 'nearest', + external: externalTooltipHandler, + }, + }, + scales: { + y: { + beginAtZero: true, + }, + }, + }, + }); +} + +/* Внешний HTML Tooltip | Потом изменить */ +const getOrCreateTooltip = (chart) => { + let tooltipEl = data.chart.canvas.parentNode.querySelector('div'); + + if (!tooltipEl) { + tooltipEl = document.createElement('div'); + tooltipEl.style.background = 'rgba(0, 0, 0, 0.4)'; + tooltipEl.style.borderRadius = '3px'; + tooltipEl.style.color = 'white'; + tooltipEl.style.opacity = 1; + tooltipEl.style.pointerEvents = 'none'; + tooltipEl.style.position = 'absolute'; + tooltipEl.style.transform = 'translate(-50%, 0)'; + tooltipEl.style.transition = 'all .1s ease'; + + const table = document.createElement('table'); + table.style.margin = '0px'; + + tooltipEl.appendChild(table); + data.chart.canvas.parentNode.appendChild(tooltipEl); + } + + return tooltipEl; +}; + +const externalTooltipHandler = (context) => { + // Tooltip Element + const { chart, tooltip } = context; + const tooltipEl = getOrCreateTooltip(data.chart); + + // Hide if no tooltip + if (tooltip.opacity === 0) { + tooltipEl.style.opacity = 0; + return; + } + + // Set Text + if (tooltip.body) { + const titleLines = tooltip.title || []; + const bodyLines = tooltip.body.map((b) => b.lines); + + const tableHead = document.createElement('thead'); + + titleLines.forEach((title) => { + const tr = document.createElement('tr'); + tr.style.borderWidth = 0; + + const th = document.createElement('th'); + th.style.borderWidth = 0; + const text = document.createTextNode(title); + + th.appendChild(text); + tr.appendChild(th); + tableHead.appendChild(tr); + }); + + const tableBody = document.createElement('tbody'); + bodyLines.forEach((body, i) => { + const colors = tooltip.labelColors[i]; + + const span = document.createElement('span'); + span.style.background = colors.backgroundColor; + span.style.borderColor = colors.borderColor; + span.style.borderWidth = '2px'; + span.style.marginRight = '10px'; + span.style.height = '10px'; + span.style.width = '10px'; + span.style.display = 'inline-block'; + + const tr = document.createElement('tr'); + tr.style.backgroundColor = 'inherit'; + tr.style.borderWidth = 0; + + const td = document.createElement('td'); + td.style.borderWidth = 0; + + const text = document.createTextNode(body); + + td.appendChild(span); + td.appendChild(text); + tr.appendChild(td); + tableBody.appendChild(tr); + }); + + const tableRoot = tooltipEl.querySelector('table'); + + // Remove old children + while (tableRoot.firstChild) { + tableRoot.firstChild.remove(); + } + + // Add new children + tableRoot.appendChild(tableHead); + tableRoot.appendChild(tableBody); + } + + const { offsetLeft: positionX, offsetTop: positionY } = data.chart.canvas; + + // Display, position, and set styles for font + tooltipEl.style.opacity = 1; + tooltipEl.style.left = positionX + tooltip.caretX + 'px'; + tooltipEl.style.top = positionY + tooltip.caretY + 'px'; + tooltipEl.style.font = tooltip.options.bodyFont.string; + tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px'; +}; diff --git a/src/data.json b/src/data.json new file mode 100644 index 0000000..3215da0 --- /dev/null +++ b/src/data.json @@ -0,0 +1,19 @@ +{ + "update": 1733593642, + "2024": { + "12": { + "01": ["10.54pm"], + "03": ["12.47am", "1.40am", "11.56am", "12.22pm", "4.51pm", "5.33pm", "1.23pm", "2.20pm", "7.07pm", "8.39pm"], + "04": ["11.49am"], + "05": ["12.13am", "12.48am", "02.03am", "2.27am", "9.44pm", "9.55pm", "10.17pm", "10.41pm", "11.48pm"], + "06": ["12.37am", "02.01am"], + "07": ["2.38am", "2.59am", "3.05am", "4.06pm", "7.30pm"] + }, + "11": { + "25": ["2.10pm", "2.30pm", "много"], + "27": ["2.33pm"], + "29": ["2.14am", "2.20am", "2.24am", "4.32am"], + "30": ["1.11am"] + } + } +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..5affebc --- /dev/null +++ b/src/index.html @@ -0,0 +1,42 @@ + + + + + + + Poppers Tracker + + + + + +
+ + 🧪 Poppers Tracker 📊 + +
+ +
+
+
+

График

+
+ + +
+
+ + +
+
+
+ +
+
+
+ +