🏴☠️ Start
This commit is contained in:
commit
62d59fd06f
28
.editorconfig
Normal file
28
.editorconfig
Normal file
@ -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
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
src/bootstrap*.*
|
||||
src/chart*.js
|
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@ -0,0 +1 @@
|
||||
*.md
|
10
.prettierrc.json
Normal file
10
.prettierrc.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"printWidth": 132,
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": true,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "always",
|
||||
"htmlWhitespaceSensitivity": "strict",
|
||||
"endOfLine": "lf"
|
||||
}
|
24
LICENSE
Normal file
24
LICENSE
Normal file
@ -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 <http://unlicense.org>
|
13
README.md
Normal file
13
README.md
Normal file
@ -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
|
27
package-lock.json
generated
Normal file
27
package-lock.json
generated
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
package.json
Normal file
5
package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"prettier": "3.4.1"
|
||||
}
|
||||
}
|
233
src/app.js
Normal file
233
src/app.js
Normal file
@ -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';
|
||||
};
|
19
src/data.json
Normal file
19
src/data.json
Normal file
@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
42
src/index.html
Normal file
42
src/index.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!doctype html>
|
||||
<html lang="ru" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="author" content="Alexander Popov <iiiypuk {at} fastmail.fm>" />
|
||||
<title>Poppers Tracker</title>
|
||||
<link rel="stylesheet" type="text/css" href="bootstrap.min.css" />
|
||||
<script src="chart-4.4.7.umd.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="p-4 border-bottom">
|
||||
<a href="/" class="link-body-emphasis text-decoration-none">
|
||||
<span class="fs-4">🧪 Poppers Tracker 📊</span>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<main class="p-4">
|
||||
<div class="d-grid gap-3" style="grid-template-columns: 1fr 3fr">
|
||||
<div class="bg-body-tertiary border rounded-3 p-3">
|
||||
<p class="fs-5 fw-bold text-center">График</p>
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="yearSelect">Год</label>
|
||||
<select class="form-select" id="yearSelect">
|
||||
<option selected>Выбрать</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="monthSelect">Месяц</label>
|
||||
<select class="form-select" id="monthSelect">
|
||||
<option selected>Выбрать</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-body-tertiary border rounded-3 p-3">
|
||||
<canvas id="chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user