Latest updates
This commit is contained in:
parent
aace75679a
commit
b2208a7e58
15 changed files with 1267 additions and 131 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
|
dist/
|
52
dist/index.html
vendored
52
dist/index.html
vendored
|
@ -1,52 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
||||||
<title>NCounter</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
background-color: rgba(0,0,0,0.25);
|
|
||||||
|
|
||||||
z-index: 12;
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-content {
|
|
||||||
background-color: #E0E0E0;
|
|
||||||
pointer-events: all;
|
|
||||||
|
|
||||||
width: 250px;
|
|
||||||
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
|
||||||
padding: 5px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script src="index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
1
dist/index.js
vendored
1
dist/index.js
vendored
File diff suppressed because one or more lines are too long
987
package-lock.json
generated
987
package-lock.json
generated
File diff suppressed because it is too large
Load diff
20
package.json
20
package.json
|
@ -5,16 +5,30 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"build": "webpack",
|
"build": "webpack --config webpack.prod.js",
|
||||||
"start": "webpack-dev-server --open"
|
"start": "webpack-dev-server --open --config webpack.dev.js"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@beyonk/google-fonts-webpack-plugin": "^1.2.3",
|
||||||
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
|
"css-loader": "^3.4.2",
|
||||||
|
"google-fonts-webpack-plugin": "^0.4.4",
|
||||||
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"idb-keyval": "^3.2.0",
|
||||||
|
"serviceworker-webpack-plugin": "^1.0.1",
|
||||||
|
"style-loader": "^1.1.3",
|
||||||
"svelte": "^3.4.1",
|
"svelte": "^3.4.1",
|
||||||
"svelte-loader": "^2.13.4",
|
"svelte-loader": "^2.13.4",
|
||||||
|
"uuid": "^3.4.0",
|
||||||
"webpack": "^4.31.0",
|
"webpack": "^4.31.0",
|
||||||
"webpack-cli": "^3.3.2",
|
"webpack-cli": "^3.3.2",
|
||||||
"webpack-dev-server": "^3.4.1"
|
"webpack-dev-server": "^3.4.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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
import App from './ncounter/App.svelte';
|
import App from './ncounter/App.svelte';
|
||||||
|
import runtime from 'serviceworker-webpack-plugin/lib/runtime';
|
||||||
|
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
const registration = runtime.register();
|
||||||
|
}
|
||||||
|
|
||||||
new App({ target: document.body });
|
new App({ target: document.body });
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="app">
|
<div class="app">
|
||||||
{#each counters as counter}
|
{#each $counters as counter}
|
||||||
<Counter counter={ counter } on:save={() => { save(counter); } }></Counter>
|
<Counter counterId={ counter.id }></Counter>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<button class="add" on:click="{ showAdd }"><i class="material-icons">add</i></button>
|
<button class="add" on:click="{ showAdd }"><i class="material-icons">add</i></button>
|
||||||
|
@ -12,38 +12,19 @@
|
||||||
<label>Target: <input type="number" bind:value={newCounter.max} /></label>
|
<label>Target: <input type="number" bind:value={newCounter.max} /></label>
|
||||||
<label>Click counter: <input type="checkbox" bind:value={newCounter.isClickCounter} /></label>
|
<label>Click counter: <input type="checkbox" bind:value={newCounter.isClickCounter} /></label>
|
||||||
<button on:click={ () => { showAddDialog = false; } }>Cancel</button>
|
<button on:click={ () => { showAddDialog = false; } }>Cancel</button>
|
||||||
<button class="ok" on:click={ addCounter }>OK</button>
|
<button class="ok" on:click={ add }>OK</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<script>
|
<script>
|
||||||
import Counter from './Counter.svelte';
|
import Counter from './Counter.svelte';
|
||||||
|
import { counters } from './db.js';
|
||||||
|
import './site.css';
|
||||||
|
|
||||||
let showAddDialog = false;
|
let showAddDialog = false;
|
||||||
|
|
||||||
let newCounter = null;
|
let newCounter = null;
|
||||||
|
|
||||||
let counters = [
|
|
||||||
{
|
|
||||||
title: 'Test 1',
|
|
||||||
value: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Test 2',
|
|
||||||
value: 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Max Test',
|
|
||||||
value: 0,
|
|
||||||
max: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Click Test',
|
|
||||||
value: 0,
|
|
||||||
isClickCounter: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
function focus(node){
|
function focus(node){
|
||||||
node.focus();
|
node.focus();
|
||||||
}
|
}
|
||||||
|
@ -56,7 +37,8 @@ function showAdd(){
|
||||||
newCounter = {
|
newCounter = {
|
||||||
title: '',
|
title: '',
|
||||||
value: 0,
|
value: 0,
|
||||||
isClickCounter: false
|
isClickCounter: false,
|
||||||
|
history: []
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log({ newCounter });
|
console.log({ newCounter });
|
||||||
|
@ -64,8 +46,8 @@ function showAdd(){
|
||||||
showAddDialog = true;
|
showAddDialog = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCounter(){
|
function add(){
|
||||||
counters = [...counters, newCounter];
|
counters.add(newCounter);
|
||||||
|
|
||||||
showAddDialog = false;
|
showAddDialog = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
{#if !counter.isClickCounter}
|
{#if !counter.isClickCounter}
|
||||||
<button on:click={ () => { showIncrement = true; } }><i class="material-icons">add</i></button>
|
<button on:click={ () => { showIncrement = true; } }><i class="material-icons">add</i></button>
|
||||||
{/if}
|
{/if}
|
||||||
|
<span class="message">{ message }</span>
|
||||||
<button class="reset" on:click={ reset }><i class="material-icons">undo</i></button>
|
<button class="reset" on:click={ reset }><i class="material-icons">undo</i></button>
|
||||||
<button on:click={ save }><i class="material-icons">save</i></button>
|
<button on:click={ save }><i class="material-icons">save</i></button>
|
||||||
|
<button on:click={ remove }><i class="material-icons">delete</i></button>
|
||||||
</div>
|
</div>
|
||||||
{#if showIncrement}
|
{#if showIncrement}
|
||||||
<div class="dialog">
|
<div class="dialog">
|
||||||
|
@ -25,20 +27,43 @@
|
||||||
import MyProgress from './MyProgress.svelte';
|
import MyProgress from './MyProgress.svelte';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { counters } from './db.js';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let counter;
|
export let counterId;
|
||||||
|
|
||||||
|
$: counter = $counters.find(x => x.id == counterId);
|
||||||
|
|
||||||
let incrementValue;
|
let incrementValue;
|
||||||
let showIncrement;
|
let showIncrement;
|
||||||
|
let message = '';
|
||||||
|
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US',
|
||||||
|
{
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric'
|
||||||
|
});
|
||||||
|
|
||||||
function focus(node){
|
function focus(node){
|
||||||
node.focus();
|
node.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function update(newCounter){
|
||||||
|
counters.update(newCounter);
|
||||||
|
}
|
||||||
|
|
||||||
function increment(){
|
function increment(){
|
||||||
counter.value += incrementValue;
|
const newCounter = {
|
||||||
|
...counter,
|
||||||
|
value: counter.value + incrementValue
|
||||||
|
}
|
||||||
|
|
||||||
|
update(newCounter);
|
||||||
|
|
||||||
showIncrement = false;
|
showIncrement = false;
|
||||||
incrementValue = null;
|
incrementValue = null;
|
||||||
}
|
}
|
||||||
|
@ -46,22 +71,52 @@ function increment(){
|
||||||
function reset(e){
|
function reset(e){
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if(confirm(`Reset ${counter.title}?`))
|
if(confirm(`Reset ${counter.title}?`)){
|
||||||
counter.value = 0;
|
const newCounter = {
|
||||||
|
...counter,
|
||||||
|
value: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
update(newCounter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(e) {
|
function save(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
dispatch('save');
|
const time = new Date();
|
||||||
counter.value = 0;
|
|
||||||
|
const data = {
|
||||||
|
time,
|
||||||
|
value: counter.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCounter = {
|
||||||
|
...counter,
|
||||||
|
history: [...counter.history, data]
|
||||||
|
}
|
||||||
|
|
||||||
|
message = `Saved ${data.value} at ${formatter.format(data.time)}`;
|
||||||
|
setTimeout(() => message = '', 5000);
|
||||||
|
|
||||||
|
update(newCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(){
|
||||||
|
if(confirm(`Remove ${counter.title}?`))
|
||||||
|
counters.remove(counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickCount() {
|
function clickCount() {
|
||||||
if(!counter.isClickCounter)
|
if(!counter.isClickCounter)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
counter.value++;
|
const newCounter = {
|
||||||
|
...counter,
|
||||||
|
value: counter.value + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
update(newCounter);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
@ -79,8 +134,9 @@ function clickCount() {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reset {
|
.message {
|
||||||
margin-left: auto;
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
|
|
57
src/ncounter/db.js
Normal file
57
src/ncounter/db.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { set as setDb, get as getDb } from 'idb-keyval';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import uuid from 'uuid/v4';
|
||||||
|
|
||||||
|
function createCounters(){
|
||||||
|
const {subscribe,set,update:updateVal} = writable([]);
|
||||||
|
|
||||||
|
getDb('counters').then(counters => {
|
||||||
|
if(counters)
|
||||||
|
set(counters);
|
||||||
|
});
|
||||||
|
|
||||||
|
function add(counter){
|
||||||
|
const newCounter = { id: uuid(), ...counter };
|
||||||
|
|
||||||
|
updateVal(counters => {
|
||||||
|
const newCounters = [...counters, newCounter];
|
||||||
|
|
||||||
|
setDb('counters', newCounters);
|
||||||
|
|
||||||
|
return newCounters;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(counter){
|
||||||
|
updateVal(counters => {
|
||||||
|
const counterIndex = counters.findIndex(x => x.id == counter.id);
|
||||||
|
|
||||||
|
const newCounters = [...counters.slice(0, counterIndex), counter, ...counters.slice(counterIndex + 1)];
|
||||||
|
|
||||||
|
setDb('counters', newCounters);
|
||||||
|
|
||||||
|
return newCounters;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(counter){
|
||||||
|
updateVal(counters => {
|
||||||
|
const counterIndex = counters.findIndex(x => x.id == counter.id);
|
||||||
|
|
||||||
|
const newCounters = [...counters.slice(0, counterIndex), ...counters.slice(counterIndex + 1)];
|
||||||
|
|
||||||
|
setDb('counters', newCounters);
|
||||||
|
|
||||||
|
return newCounters;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
add,
|
||||||
|
update,
|
||||||
|
remove
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const counters = createCounters();
|
40
src/ncounter/site.css
Normal file
40
src/ncounter/site.css
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
background-color: rgba(0,0,0,0.25);
|
||||||
|
|
||||||
|
z-index: 12;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
background-color: #E0E0E0;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
41
src/sw.js
Normal file
41
src/sw.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import {registerRoute} from 'workbox-routing';
|
||||||
|
import {CacheFirst, StaleWhileRevalidate, NetworkFirst} from 'workbox-strategies';
|
||||||
|
import {CacheableResponsePlugin} from 'workbox-cacheable-response';
|
||||||
|
import {ExpirationPlugin} from 'workbox-expiration';
|
||||||
|
|
||||||
|
// Cache the Google Fonts stylesheets with a stale-while-revalidate strategy.
|
||||||
|
registerRoute(
|
||||||
|
/^https:\/\/fonts\.googleapis\.com/,
|
||||||
|
new StaleWhileRevalidate({
|
||||||
|
cacheName: 'google-fonts-stylesheets',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cache the underlying font files with a cache-first strategy for 1 year.
|
||||||
|
registerRoute(
|
||||||
|
/^https:\/\/fonts\.gstatic\.com/,
|
||||||
|
new CacheFirst({
|
||||||
|
cacheName: 'google-fonts-webfonts',
|
||||||
|
plugins: [
|
||||||
|
new CacheableResponsePlugin({
|
||||||
|
statuses: [0, 200],
|
||||||
|
}),
|
||||||
|
new ExpirationPlugin({
|
||||||
|
maxAgeSeconds: 60 * 60 * 24 * 365,
|
||||||
|
maxEntries: 30,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
registerRoute(
|
||||||
|
/\.(?:js|css)$/,
|
||||||
|
new StaleWhileRevalidate({
|
||||||
|
cacheName: 'static-resources',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
registerRoute(
|
||||||
|
'/',
|
||||||
|
new NetworkFirst()
|
||||||
|
)
|
44
webpack.common.js
Normal file
44
webpack.common.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
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');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './src/index.js',
|
||||||
|
plugins: [
|
||||||
|
new CleanWebpackPlugin(),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
title: 'NCounter',
|
||||||
|
}),
|
||||||
|
new GoogleFontsPlugin({
|
||||||
|
fonts: [
|
||||||
|
{ family: "Material Icons" }
|
||||||
|
],
|
||||||
|
local: false
|
||||||
|
}),
|
||||||
|
new ServiceWorkerWebpackPlugin({
|
||||||
|
entry: path.join(__dirname, 'src/sw.js'),
|
||||||
|
})
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
filename: 'index.js',
|
||||||
|
path: path.resolve(__dirname, 'dist')
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.svelte$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader: 'svelte-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: './src/index.js',
|
|
||||||
output: {
|
|
||||||
filename: 'index.js',
|
|
||||||
path: path.resolve(__dirname, 'dist')
|
|
||||||
},
|
|
||||||
devServer: {
|
|
||||||
contentBase: './dist'
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.svelte$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loader: 'svelte-loader'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
devtool: 'inline-source-map'
|
|
||||||
}
|
|
10
webpack.dev.js
Normal file
10
webpack.dev.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
const merge = require('webpack-merge');
|
||||||
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
mode: 'development',
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
devServer: {
|
||||||
|
contentBase: './dist',
|
||||||
|
}
|
||||||
|
});
|
6
webpack.prod.js
Normal file
6
webpack.prod.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
const merge = require('webpack-merge');
|
||||||
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
mode: 'production',
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue