Compare commits

...

10 commits

Author SHA1 Message Date
602dbf5f3f Bring back history, without the chart 2024-04-12 12:41:58 -04:00
4679d04076 Remove html-loader from packages 2024-04-12 12:36:43 -04:00
1879d3a420 remove html-loader and add static font link 2024-04-12 12:36:43 -04:00
e52dbb6b9a Package API changes 2024-04-12 12:36:43 -04:00
d057048c66 Update packages 2024-04-12 12:36:43 -04:00
4a572324e0 uuid API changed 2024-04-12 12:36:43 -04:00
bf43bdba09 Add nix config 2024-04-12 12:36:43 -04:00
5b504ae3d6 Update packages 2024-04-12 12:36:43 -04:00
75f80460f5 Bump chart.js from 2.9.3 to 2.9.4
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 2.9.3 to 2.9.4.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v2.9.3...v2.9.4)

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-12 12:36:43 -04:00
7fa7dd4e0f Create UI to prompt for reload when an
update is available
2024-04-12 12:36:43 -04:00
14 changed files with 5376 additions and 6226 deletions

11255
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,31 +11,30 @@
"author": "",
"license": "MIT",
"devDependencies": {
"@beyonk/google-fonts-webpack-plugin": "^1.2.3",
"chart.js": "^2.9.3",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.2",
"file-loader": "^5.1.0",
"google-fonts-webpack-plugin": "^0.4.4",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"idb-keyval": "^3.2.0",
"reset-css": "^5.0.1",
"serviceworker-webpack-plugin": "^1.0.1",
"string-replace-loader": "^2.2.0",
"style-loader": "^1.1.3",
"svelte": "^3.4.1",
"svelte-loader": "^2.13.4",
"uuid": "^3.4.0",
"webpack": "^4.31.0",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.4.1",
"webpack-license-plugin": "^4.1.1",
"webpack-merge": "^4.2.2",
"workbox-cacheable-response": "^5.0.0",
"workbox-expiration": "^5.0.0",
"workbox-routing": "^5.0.0",
"workbox-strategies": "^5.0.0"
"chart.js": "^4.4.2",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.1",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.6.0",
"idb-keyval": "^6.2.1",
"reset-css": "^5.0.2",
"string-replace-loader": "^3.1.0",
"style-loader": "^4.0.0",
"svelte": "^4.2.13",
"svelte-loader": "^3.2.0",
"terser-webpack-plugin": "^5.3.10",
"uuid": "^9.0.1",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4",
"webpack-license-plugin": "^4.4.2",
"webpack-merge": "^5.10.0",
"workbox-broadcast-update": "^7.0.0",
"workbox-cacheable-response": "^7.0.0",
"workbox-expiration": "^7.0.0",
"workbox-routing": "^7.0.0",
"workbox-strategies": "^7.0.0",
"workbox-window": "^7.0.0"
}
}

7
shell.nix Normal file
View file

@ -0,0 +1,7 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
# nativeBuildInputs is usually what you want -- tools you need to run
nativeBuildInputs = with pkgs.buildPackages; [
nodejs_18
];
}

View file

@ -5,7 +5,16 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Web app for keeping track of anything you want to count.">
<title>NCounter</title>
${require('./icon/html_code.html')}
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="manifest" href="icons/site.webmanifest">
<link rel="mask-icon" href="icons/safari-pinned-tab.svg" color="#3a86b7">
<link rel="shortcut icon" href="icons/favicon.ico">
<meta name="msapplication-TileColor" content="#dce6ef">
<meta name="msapplication-config" content="icons/browserconfig.xml">
<meta name="theme-color" content="#dce6ef">
</head>
<body>
<noscript>

View file

@ -1,10 +1,18 @@
import App from './ncounter/App.svelte';
import runtime from 'serviceworker-webpack-plugin/lib/runtime';
import 'reset-css/reset.css';
import './ncounter/site.css';
import {Workbox} from 'workbox-window/Workbox.mjs';
const app = new App({ target: document.body });
if ('serviceWorker' in navigator) {
const registration = runtime.register();
}
const wb = new Workbox('sw.js');
new App({ target: document.body });
wb.addEventListener('message', async (event) => {
if (event.data.type === 'CACHE_UPDATED') {
app.SetUpdateAvailable();
}
});
wb.register();
}

View file

@ -1,5 +1,5 @@
<div class="about">
<button class="close" on:click={ () => { dispatch('close'); } }><i class="material-icons">close</i></button>
<button class="close" on:click={ () => { dispatch('close'); } }><span class="material-symbols-outlined">close</span></button>
<header>
<h1>NCounter</h1>
<h2>Release |BUILD_DATE|</h2>

View file

@ -4,8 +4,16 @@
<Counter counterId={ counter.id }></Counter>
{/each}
</div>
{#if showUpdateMessage}
<div class="update-message">
An update is available. <button class="reload" on:click="{ () => { window.location.reload(); } }">Load now</button><button class="later" on:click="{ () => { showUpdateMessage = false; } }">Later</button>
</div>
{/if}
<div class="tool-bar">
<button class="add" on:click="{ showAdd }"><i class="material-icons">add</i></button>
<button class="add" on:click="{ showAdd }"><span class="material-symbols-outlined">add</span></button>
{#if updateAvailable}<button class="update"
class:animate="{!ignoreUpdate}"
on:click={() => { ignoreUpdate = true; showUpdateMessage = !showUpdateMessage; }}><span class="material-symbols-outlined">arrow_upward</span></button>{/if}
<button class="about" on:click="{ () => { showAbout = true; } }">?</button>
</div>
</div>
@ -36,6 +44,10 @@ let showAbout = false;
let newCounter = null;
let dialogForm = null;
let updateAvailable = false;
let ignoreUpdate = false;
let showUpdateMessage = false;
function focus(node){
node.focus();
}
@ -68,6 +80,10 @@ function add(){
showAddDialog = false;
}
export function SetUpdateAvailable(){
updateAvailable = true;
}
</script>
<style lang="scss">
.app {
@ -117,8 +133,71 @@ function add(){
border: 2px solid var(--highlight-color);
}
.add {
margin-right: auto;
}
@keyframes pulse {
0% {
-webkit-text-stroke: 0 rgba(204, 204, 0, 0);
}
15% {
-webkit-text-stroke: 3px rgba(204, 204, 0, 1);
}
30% {
-webkit-text-stroke: 0 rgba(204, 204, 0, 0);
}
}
.update {
margin-right: 10px;
}
.update.animate {
animation-name: pulse;
animation-duration: 5s;
animation-iteration-count: infinite;
}
.about {
margin-left: auto;
font-size: 20px;
}
.update-message {
display: flex;
align-items: center;
padding: 12px;
background-color: var(--background-primary);
color: var(--text-primary);
box-shadow: 0 -1px 3px rgba(0,0,0,0.12), 0 -1px 2px rgba(0,0,0,0.24);
}
.update-message > button {
width: auto;
height: 24px;
font-size: 16px;
}
@media(hover: hover){
.update-message > button:hover {
outline: 2px solid var(--button-color);
}
}
.update-message > button:focus {
outline: 2px solid var(--button-color);
}
.reload {
margin-left: auto;
}
.later {
margin-left: 16px;
}
</style>

View file

@ -5,14 +5,14 @@
<MyProgress value={ counter.value } max={ counter.max }></MyProgress>
{/if}
<div class="controls">
<button on:click={ () => { showIncrement = true; } }><i class="material-icons">add</i></button>
<button on:click={ () => { showSet = true; } }><i class="material-icons">edit</i></button>
<button class="reset" on:click={ reset }><i class="material-icons">replay</i></button>
<button on:click={ () => { showIncrement = true; } }><span class="material-symbols-outlined">add</span></button>
<button on:click={ () => { showSet = true; } }><span class="material-symbols-outlined">edit</span></button>
<button class="reset" on:click={ reset }><span class="material-symbols-outlined">replay</span></button>
{#if counter.saveHistory}
<button class="history" on:click={ () => { showHistory = true; } }><i class="material-icons">show_chart</i></button>
<button class="history" on:click={ () => { showHistory = true; } }><span class="material-symbols-outlined">show_chart</span></button>
{/if}
<button on:click={ startEdit }><i class="material-icons">settings</i></button>
<button on:click={ remove }><i class="material-icons">delete</i></button>
<button on:click={ startEdit }><span class="material-symbols-outlined">settings</span></button>
<button on:click={ remove }><span class="material-symbols-outlined">delete</span></button>
</div>
{#if showValueDialog}
<div class="dialog">
@ -47,7 +47,6 @@
<script>
import MyProgress from './MyProgress.svelte';
import History from './History.svelte';
import { tick } from 'svelte';
import { counters } from './db.js';
export let counterId;

View file

@ -1,10 +1,8 @@
<div class="history-container">
<header>
<h1>{ counter.title }</h1>
<button class="close" on:click={ close }><i class="material-icons">close</i></button>
<button class="close" on:click={ close }><span class="material-symbols-outlined">close</span></button>
</header>
<canvas class="chart-container" bind:this={ chartEl } width="{ window.outerWidth }" height="250">
</canvas>
<div class="table-container">
<table class="history">
<thead>
@ -27,7 +25,6 @@
<script>
import { createEventDispatcher } from 'svelte';
import { counters } from './db.js';
import Chart from 'chart.js';
const dispatch = createEventDispatcher();
@ -47,77 +44,9 @@ $: history = counter.history.map(x => {
value: x.value
}
});
$: dataset = counter.history.reduce((acc, x) => {
return {
labels: [...acc.labels, x.time],
datasets: [
{
...acc.datasets[0],
data: [...acc.datasets[0].data, x.value]
}
]
}
}, {
labels: [],
datasets: [
{
borderColor: darkMode ? 'rgba(255, 255, 255, 0.25)' : undefined,
backgroundColor: darkMode ? 'rgba(255, 255, 255, 0.25)' : undefined,
lineTension: 0.1,
data: []
}
]
});
export let counterId;
let chartEl;
const themeMatch = window.matchMedia('(prefers-color-scheme: dark)');
let darkMode = themeMatch.matches;
themeMatch.addListener((e) => {
darkMode = e.matches;
});
$: {
if(chartEl){
const theme = darkMode ?
{
gridLines: {
color: 'rgba(255, 255, 255, 0.1)',
zeroLineColor: 'rgba(255, 255, 255, 0.25)'
},
ticks: {
fontColor: '#FFFFFF'
}
} :
{} //Use defaults for light mode;
new Chart(chartEl, {
type: 'line',
data: dataset,
options: {
scales: {
xAxes: [
{
...theme,
type: 'time'
}
],
yAxes: [
{
...theme
}
]
},
legend: {
display: false
}
}
});
}
}
function close(){
dispatch('close');
}

View file

@ -1,6 +1,6 @@
import { set as setDb, get as getDb } from 'idb-keyval';
import { writable } from 'svelte/store';
import uuid from 'uuid/v4';
import { v4 as uuid } from 'uuid';
function createCounters(){
const {subscribe,set,update:updateVal} = writable([]);

View file

@ -2,6 +2,7 @@ import {registerRoute,setDefaultHandler} from 'workbox-routing';
import {CacheFirst, StaleWhileRevalidate, NetworkFirst} from 'workbox-strategies';
import {CacheableResponsePlugin} from 'workbox-cacheable-response';
import {ExpirationPlugin} from 'workbox-expiration';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';
// Cache the Google Fonts stylesheets with a stale-while-revalidate strategy.
registerRoute(
@ -32,6 +33,9 @@ registerRoute(
/\.(?:js|css|png)$/,
new StaleWhileRevalidate({
cacheName: 'static-resources',
plugins: [
new BroadcastUpdatePlugin(),
],
})
);

View file

@ -1,8 +1,6 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const GoogleFontsPlugin = require('@beyonk/google-fonts-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin');
const LicensePlugin = require('webpack-license-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
@ -11,55 +9,43 @@ const year = now.getFullYear().toString();
const releaseDate = now.getFullYear() + "-" + (now.getMonth() + 1).toString().padStart(2, '0') + "-" + now.getDate().toString().padStart(2, '0');
module.exports = {
entry: './src/index.js',
entry: {
index: './src/index.js',
sw: './src/sw.js'
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'NCounter',
excludeChunks: ['sw'],
template: 'src/index.html'
}),
new GoogleFontsPlugin({
fonts: [
{ family: "Material Icons" }
],
local: false
}),
new ServiceWorkerWebpackPlugin({
entry: path.join(__dirname, 'src/sw.js'),
publicPath: './'
}),
new LicensePlugin(),
new CopyWebpackPlugin([
{
from: 'src/icon/*.png', to: 'icons', flatten: true
},
{
from: 'src/icon/safari-pinned-tab.svg', to: 'icons'
},
{
from: 'src/icon/favicon.ico', to: 'icons'
},
{
from: 'src/icon/site.webmanifest', to: 'icons'
},
{
from: 'src/icon/browserconfig.xml', to: 'icons'
}
])
new CopyWebpackPlugin({
patterns: [
{
from: 'src/icon/*.png', to: path.resolve(__dirname, 'dist', 'icons', '[name][ext]')
},
{
from: 'src/icon/safari-pinned-tab.svg', to: 'icons'
},
{
from: 'src/icon/favicon.ico', to: 'icons'
},
{
from: 'src/icon/site.webmanifest', to: 'icons'
},
{
from: 'src/icon/browserconfig.xml', to: 'icons'
}
]
})
],
output: {
filename: 'index.js',
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.html$/,
loader: 'html-loader',
options: {
interpolate: true
}
},
{
test: /\.svelte$/,
exclude: /node_modules/,
@ -89,4 +75,7 @@ module.exports = {
}
]
},
resolve: {
conditionNames: ['svelte']
},
}

View file

@ -1,10 +1,10 @@
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
static: './dist',
}
});

View file

@ -1,6 +1,18 @@
const merge = require('webpack-merge');
const webpack = require('webpack');
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const Terser = require('terser-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
],
optimization:{
minimizer: [new Terser({
test: /\.m?js$/,
})]
}
});