init
This commit is contained in:
commit
619e6ee229
67
.eslintrc.json
Executable file
67
.eslintrc.json
Executable file
@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2019,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"es6": true,
|
||||||
|
"browser": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"svelte3"
|
||||||
|
],
|
||||||
|
"ignorePatterns": [
|
||||||
|
"public/build/"
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/*.svelte"],
|
||||||
|
"processor": "svelte3/svelte3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"arrow-spacing": "error",
|
||||||
|
"block-scoped-var": "error",
|
||||||
|
"block-spacing": "error",
|
||||||
|
"brace-style": ["error", "stroustrup", {}],
|
||||||
|
"camelcase": "error",
|
||||||
|
"comma-dangle": ["error", "never"],
|
||||||
|
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||||
|
"comma-style": [1, "last"],
|
||||||
|
"consistent-this": [1, "_this"],
|
||||||
|
"curly": [1, "multi"],
|
||||||
|
"eol-last": 1,
|
||||||
|
"eqeqeq": 1,
|
||||||
|
"func-names": 1,
|
||||||
|
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||||
|
"lines-around-comment": ["error", { "beforeBlockComment": true, "allowArrayStart": true }],
|
||||||
|
"max-len": [1, 240, 2], // 2 spaces per tab, max 80 chars per line
|
||||||
|
"new-cap": 1,
|
||||||
|
"newline-before-return": "error",
|
||||||
|
"no-array-constructor": 1,
|
||||||
|
"no-inner-declarations": [1, "both"],
|
||||||
|
"no-mixed-spaces-and-tabs": 1,
|
||||||
|
"no-multi-spaces": 2,
|
||||||
|
"no-new-object": 1,
|
||||||
|
"no-shadow-restricted-names": 1,
|
||||||
|
"object-curly-spacing": ["error", "always"],
|
||||||
|
"padded-blocks": ["error", { "blocks": "never", "switches": "always" }],
|
||||||
|
"prefer-const": "error",
|
||||||
|
"prefer-template": "error",
|
||||||
|
"one-var": 0,
|
||||||
|
"quote-props": ["error", "always"],
|
||||||
|
"quotes": [1, "single"],
|
||||||
|
"radix": 1,
|
||||||
|
"semi": [1, "always"],
|
||||||
|
"space-before-blocks": [1, "always"],
|
||||||
|
"space-infix-ops": 1,
|
||||||
|
"vars-on-top": 1,
|
||||||
|
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
|
||||||
|
"spaced-comment": ["error", "always", { "markers": ["/"] }]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
4
.gitignore
vendored
Executable file
4
.gitignore
vendored
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
/node_modules/
|
||||||
|
# /public/build/
|
||||||
|
|
||||||
|
.DS_Store
|
18
Docker/Dockerfile
Normal file
18
Docker/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# FROM node:current-slim
|
||||||
|
FROM node:current-alpine
|
||||||
|
ARG VERSION
|
||||||
|
ENV VERSION ${VERSION:-development}
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ./start.sh ../package*.json ./ecosystem.config.json /app/
|
||||||
|
|
||||||
|
COPY ./public/ /app/public
|
||||||
|
|
||||||
|
RUN npm install pm2 -g && npm install
|
||||||
|
|
||||||
|
# RUN ls -lh .
|
||||||
|
|
||||||
|
RUN chmod +x /app/start.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/start.sh"]
|
22
Docker/ecosystem.config.json
Normal file
22
Docker/ecosystem.config.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Predictor",
|
||||||
|
"script": "app/predict.js",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "production"
|
||||||
|
},
|
||||||
|
"autorestart": false,
|
||||||
|
"instances": 1,
|
||||||
|
"cron_restart": "10 15 * * 2,5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Retriever",
|
||||||
|
"script": "app/retriever.js",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "production"
|
||||||
|
},
|
||||||
|
"autorestart": false,
|
||||||
|
"instances": 1,
|
||||||
|
"cron_restart": "45 9 * * 3,6"
|
||||||
|
}
|
||||||
|
]
|
4
Docker/start.sh
Executable file
4
Docker/start.sh
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
pm2-runtime start ecosystem.config.json --raw --env production
|
34
Makefile
Normal file
34
Makefile
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
PROJECT = slack
|
||||||
|
VERSION = $(shell git rev-parse --short HEAD)
|
||||||
|
|
||||||
|
ECR_REPO = mail.caliban.io:5000
|
||||||
|
#APP_IMAGE = 482681734622.dkr.ecr.eu-west-1.amazonaws.com/$(PROJECT):$(VERSION)
|
||||||
|
APP_IMAGE = $(ECR_REPO)/$(PROJECT):$(VERSION)
|
||||||
|
NO_CACHE = true
|
||||||
|
|
||||||
|
|
||||||
|
#build docker image
|
||||||
|
build:
|
||||||
|
npm run build
|
||||||
|
docker build .Docker/. -t $(APP_IMAGE) --build-arg VERSION=$(VERSION) --no-cache=$(NO_CACHE) --compress
|
||||||
|
.PHONY: build
|
||||||
|
|
||||||
|
#push docker image to registry
|
||||||
|
push: build
|
||||||
|
docker push $(APP_IMAGE)
|
||||||
|
.PHONY: push
|
||||||
|
|
||||||
|
#push docker image to registry
|
||||||
|
run: build
|
||||||
|
docker run $(APP_IMAGE)
|
||||||
|
.PHONY: run
|
||||||
|
ver:
|
||||||
|
@echo '$(VERSION)'
|
||||||
|
#echo $ERSION
|
||||||
|
.PHONY: ver
|
||||||
|
|
||||||
|
tar:
|
||||||
|
# docker build . -t $(APP_IMAGE) --build-arg VERSION=$(VERSION) --no-cache=$(NO_CACHE)
|
||||||
|
tar -C ./ -czvf ./archive.tar.gz 'package.json' 'ncas/' 'helpers/' -X *.js
|
||||||
|
|
||||||
|
.PHONY: build
|
93
README.md
Executable file
93
README.md
Executable file
@ -0,0 +1,93 @@
|
|||||||
|
*Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# svelte app
|
||||||
|
|
||||||
|
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
|
||||||
|
|
||||||
|
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx degit sveltejs/template svelte-app
|
||||||
|
cd svelte-app
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
|
||||||
|
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
|
||||||
|
Install the dependencies...
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd svelte-app
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
...then start [Rollup](https://rollupjs.org):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
|
||||||
|
|
||||||
|
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
|
||||||
|
|
||||||
|
|
||||||
|
## Building and running in production mode
|
||||||
|
|
||||||
|
To create an optimised version of the app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
|
||||||
|
|
||||||
|
|
||||||
|
## Single-page app mode
|
||||||
|
|
||||||
|
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
|
||||||
|
|
||||||
|
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
|
||||||
|
|
||||||
|
```js
|
||||||
|
"start": "sirv public --single"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Deploying to the web
|
||||||
|
|
||||||
|
### With [now](https://zeit.co/now)
|
||||||
|
|
||||||
|
Install `now` if you haven't already:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g now
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, from within your project folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd public
|
||||||
|
now deploy --name my-project
|
||||||
|
```
|
||||||
|
|
||||||
|
As an alternative, use the [Now desktop client](https://zeit.co/download) and simply drag the unzipped project folder to the taskbar icon.
|
||||||
|
|
||||||
|
### With [surge](https://surge.sh/)
|
||||||
|
|
||||||
|
Install `surge` if you haven't already:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g surge
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, from within your project folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
surge public my-project.surge.sh
|
||||||
|
```
|
4
copy.sh
Executable file
4
copy.sh
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# rm -rf /home/martin/dev/silvrgit/dist/*
|
||||||
|
cp -r /home/martin/dev/Svelte/svelte-silvrtree/public/* /home/martin/dev/Server/silvrgit/dist
|
25
orig.eslintrc.json
Executable file
25
orig.eslintrc.json
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
module.exports = {
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2019,
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
es6: true,
|
||||||
|
browser: true
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
'svelte3'
|
||||||
|
],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
'*.svelte'
|
||||||
|
],
|
||||||
|
processor: 'svelte3/svelte3'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
}
|
||||||
|
};
|
4182
package-lock.json
generated
Executable file
4182
package-lock.json
generated
Executable file
File diff suppressed because it is too large
Load Diff
39
package.json
Executable file
39
package.json
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "slack-docker",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup -c",
|
||||||
|
"dev": "rollup -c -w",
|
||||||
|
"start": "sirv public",
|
||||||
|
"lint": "eslint . --ext .js,.svelte --fix"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^17.0.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^11.1.0",
|
||||||
|
"eslint": "^7.26.0",
|
||||||
|
"eslint-plugin-import": "^2.23.2",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-promise": "^5.1.0",
|
||||||
|
"eslint-plugin-standard": "^5.0.0",
|
||||||
|
"eslint-plugin-svelte3": "^3.2.0",
|
||||||
|
"prettier": "^2.3.0",
|
||||||
|
"prettier-plugin-svelte": "^2.3.0",
|
||||||
|
"rollup": "^2.38.0",
|
||||||
|
"rollup-plugin-css-only": "^3.1.0",
|
||||||
|
"rollup-plugin-livereload": "^2.0.0",
|
||||||
|
"rollup-plugin-svelte": "^7.1.0",
|
||||||
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
|
"svelte": "^3.32.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"fecha": "^4.2.0",
|
||||||
|
"moment": "^2.29.1",
|
||||||
|
"rollup-plugin-replace": "^2.2.0",
|
||||||
|
"sirv-cli": "^1.0.10"
|
||||||
|
},
|
||||||
|
"description": "Slack.. but in a Docker container",
|
||||||
|
"main": "rollup.config.js",
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
1
public/build/bundle.css
Executable file
1
public/build/bundle.css
Executable file
@ -0,0 +1 @@
|
|||||||
|
.timerContainer.svelte-1woqwo5.svelte-1woqwo5{border:1px solid #BDBDBD;background-color:#EEEEEE}.inner.svelte-1woqwo5.svelte-1woqwo5{display:flex;color:#333;flex-direction:column;align-items:center;justify-content:center}.timer-value.svelte-1woqwo5.svelte-1woqwo5{display:flex;color:#333;flex-direction:row;align-items:center;justify-content:center;font-size:24px;height:100%;width:100%}.timer-value.svelte-1woqwo5 small.svelte-1woqwo5{font-size:18px;margin-left:4px}.svelte-1dnn1u0{padding:3px}.up.svelte-1dnn1u0,.down.svelte-1dnn1u0{display:inline-block;background-size:contain;width:0.8em;height:0.8em}.up.svelte-1dnn1u0,.down.svelte-1dnn1u0{background-image:url("data:image/svg+xml,%3Csvg width='32' height='32' version='1.1' viewBox='0 0 8.4667 8.4667' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m0.82549 1.3138h5.6797l1.136 2e-7 -3.4078 5.8215z' style='stroke-width:.035938;stroke:%23000'/%3E%3C/svg%3E%0A")}.up.svelte-1dnn1u0{background-image:url("data:image/svg+xml,%3Csvg width='32' height='32' version='1.1' viewBox='0 0 8.4667 8.4667' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m1.3226 7.6324v-5.6797l2e-7 -1.136 5.8215 3.4078z' style='stroke-width:.035938;stroke:%23000'/%3E%3C/svg%3E%0A")}
|
20
public/build/bundle.css.map
Executable file
20
public/build/bundle.css.map
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"file": "bundle.css",
|
||||||
|
"sources": [
|
||||||
|
"../../Revealer.svelte",
|
||||||
|
"../../Fx.svelte",
|
||||||
|
"../../Route.svelte",
|
||||||
|
"../../Timer.svelte",
|
||||||
|
"../../Weather.svelte"
|
||||||
|
],
|
||||||
|
"sourcesContent": [
|
||||||
|
"<script>\n import {slide} from 'svelte/transition';\n\n let viewMode = 0;\n let icon;\n\n $: icon = (viewMode === 0) ? 'up' : 'down';\n\n function changeViewMode() {\n viewMode = (viewMode === 0) ? 1 : 0;\n }\n</script>\n\n<style>\n * {\n padding: 3px;\n }\n .pointer {cursor: pointer;}\n .up, .down{\n display:inline-block;\n background-size: contain;\n width:0.8em;\n height:0.8em;\n }\n .up, .down{\n background-image: url(\"data:image/svg+xml,%3Csvg width='32' height='32' version='1.1' viewBox='0 0 8.4667 8.4667' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m0.82549 1.3138h5.6797l1.136 2e-7 -3.4078 5.8215z' style='stroke-width:.035938;stroke:%23000'/%3E%3C/svg%3E%0A\");\n }\n .up{\n background-image: url(\"data:image/svg+xml,%3Csvg width='32' height='32' version='1.1' viewBox='0 0 8.4667 8.4667' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m1.3226 7.6324v-5.6797l2e-7 -1.136 5.8215 3.4078z' style='stroke-width:.035938;stroke:%23000'/%3E%3C/svg%3E%0A\");\n }\n</style>\n\n\n<div><div on:click={changeViewMode} class=\"pointer {icon}\"></div>\n <span on:click={changeViewMode} class=\"pointer\"><slot name=\"header\">No header was provided</slot></span>\n</div>\n{#if viewMode === 1}\n <div transition:slide>\n <slot></slot>\n </div>\n{/if}\n",
|
||||||
|
"<script>\n import {onMount} from 'svelte';\n\n const __url = (__ENV__ === 'production') ? 'https://silvrtree.co.uk' : 'http://localhost:9000';\n\n\n let fxData={};\n\n onMount(async () => {\n await update();\n });\n\n async function update() {\n await getFX();\n\n const now = new Date();\n const mod = 1800000 - (now.getTime() % 1800000);\n\n const fxUpdateFn = function () {\n update();\n };\n\n setTimeout(fxUpdateFn.bind(this), mod + 10);\n }\n\n function reduceFX(data) {\n\n if (data.rates !== undefined) {\n const gpbex = (1 / data.rates.GBP);\n const sekex = (gpbex * data.rates.SEK);\n fxData = {\n 'usd': 1,\n 'gbp': data.rates.GBP,\n 'sek': data.rates.SEK,\n 'gpbe': gpbex,\n 'sekex': sekex\n };\n }\n\n }\n\n async function getFX() {\n\n const res = await fetch(`${__url}/fx`);\n const json = await res.json();\n\n if (json) reduceFX(json);\n }\n</script>\n\n<style>\n * {\n font-size: 90%;\n }\n</style>\n\n{#if fxData.gpbe}\n<span>\n £1 = ${parseFloat(fxData.gpbe.toFixed(2))} = { parseFloat(fxData.sekex.toFixed(2))} SEK\n</span>\n\n{/if}\n\n\n",
|
||||||
|
"<script>\n import {route} from './store';\n\n const __url = (__ENV__ === 'production') ? 'https://traintimes.silvrtree.co.uk' : 'http://localhost:8100';\n\n let visible = false;\n let fromStation;\n let toStation;\n let url;\n let routeData = {};\n let services = [];\n\n route.subscribe(async (v) => {\n console.log('>> route', v);\n fromStation = v.fromStation;\n toStation = v.toStation;\n visible = (fromStation !== '') ? !visible : false;\n\n // url = `https://traintimes.silvrtree.co.uk/gettrains?from=${ fromStation }&to=${ toStation}`;\n\n url = `${__url}/gettrains?from=${fromStation}&to=${toStation}`;\n\n if (fromStation !== '' && visible) {\n await update();\n }\n });\n\n function reduceRoute(data) {\n const newData = {};\n\n newData.fromName = data.locationName;\n newData.toName = data.filterLocationName;\n newData.services = [];\n\n\n if (typeof data.trainServices === 'object' && data.trainServices !== null)\n for (const item of data.trainServices) {\n const dest = item.destination[0];\n const via = dest.via !== null ? `<em>${dest.via}</em>` : '';\n const platform = item.platform !== null ? item.platform : '💠';\n const time = item.sta !== null ? item.sta : `D ${item.std}`;\n const status = item.eta !== null ? item.eta : item.etd;\n const cls = (status.toLowerCase() === 'on time') ? 'ontime' : 'delayed';\n newData.services.push({\n 'location': dest.locationName,\n 'time': time,\n 'via': via,\n 'class': cls,\n 'status': status,\n 'platform': platform,\n 'cancelReason': item.cancelReason,\n 'type': 'train',\n 'isCancelled': item.isCancelled,\n icon: ''\n });\n }\n\n if (typeof data.busServices === 'object' && data.busServices !== null)\n for (const item of data.busServices) {\n const dest = item.destination[0];\n const via = dest.via !== null ? `<em>${dest.via}</em>` : '';\n const platform = item.platform !== null ? item.platform : '';\n const time = item.sta !== null ? item.sta : `D ${item.std}`;\n const status = item.eta !== null ? item.eta : item.etd;\n const cls = (status.toLowerCase() === 'on time') ? 'ontime' : 'delayed';\n newData.services.push({\n 'location': dest.locationName,\n 'time': time,\n 'via': via,\n 'class': cls,\n 'status': status,\n 'platform': platform,\n 'cancelReason': item.cancelReason,\n 'type': 'bus',\n 'isCancelled': item.isCancelled,\n icon: '🚌 '\n });\n }\n\n routeData = newData;\n services = newData.services;\n\n console.log(routeData);\n }\n\n async function update() {\n await getRoute();\n\n const now = new Date();\n const mod = 180000 - (now.getTime() % 180000);\n\n const routeUpdateFn = function () {\n update();\n };\n\n if(visible) setTimeout(routeUpdateFn.bind(this), mod + 10);\n\n }\n\n\n async function getRoute() {\n console.log('Get route', url);\n\n const res = await fetch(url);\n const json = await res.json();\n\n if (json) {\n console.log(json);\n // data = json;\n reduceRoute(json);\n }\n\n\n }\n\n</script>\n\n<style>\n .routeBox {\n border:1px dotted silver;\n }\n</style>\n\n{#if visible}\n<div class=\"routeBox\">\n <div>{routeData.fromName} TO {routeData.toName}</div>\n <table class=\"mui-table mui-table-bordered\">\n <thead>\n <tr>\n <th>Destination</th>\n <th>Time</th>\n <th>Status</th>\n <th>Platform</th>\n </tr>\n </thead>\n\n <tbody>\n {#each services as item}\n <tr>\n\n <td>{item.icon}{item.location} {@html item.via}</td>\n <td class={item.class}>{item.time}</td>\n {#if !item.isCancelled}\n <td class={item.class}>{item.status}</td>\n <td>{item.platform}</td>\n {:else}\n <td colspan=\"2\" class=\"delayed\">❌ {item.cancelReason}</td>\n {/if}\n </tr>\n {/each}\n </tbody>\n </table>\n\n</div>\n{/if}\n\n",
|
||||||
|
"<script>\n import {tweened} from 'svelte/motion';\n import Revealer from '@rakh/svelte-revealer';\n\n let timerVisible = false;\n let range = 25;\n let start;\n let timerID = 0;\n let snd;\n // let timer = 0;\n\n $: startVal = range * 60;\n $: timer = tweened(startVal);\n\n $: minutes = Math.floor($timer / 60);\n $: minname = minutes > 1 ? \"mins\" : \"min\";\n $: seconds = Math.floor($timer - minutes * 60)\n\n const progress = tweened(0, {duration: 1000})\n $: {\n $progress = 1 - ($timer / startVal)\n }\n\n function complete() {\n snd = new Audio('stuff/bell.mp3');\n snd.play();\n\n }\n\n function stopTimer() {\n clearInterval(timerID);\n timerID = 0;\n }\n\n function showTimer() {\n timerVisible = !timerVisible;\n if (!timerVisible) {\n // clearTimer\n stopTimer();\n }\n }\n\n function startTimer() {\n\n if (timerID !== 0) {\n stopTimer();\n } else {\n timerID = setInterval(() => {\n if ($timer > 0) $timer--\n else {\n stopTimer();\n complete();\n }\n }, 1000);\n\n }\n\n }\n\n</script>\n\n<style>\n .timerContainer {\n border: 1px solid #BDBDBD;\n background-color: #EEEEEE;\n\n }\n\n .inner {\n display: flex;\n /*mix-blend-mode: difference;*/\n color: #333;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n }\n\n .timer-value {\n display: flex;\n /*mix-blend-mode: difference;*/\n color: #333;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n font-size: 24px;\n height: 100%;\n width: 100%;\n }\n\n .timer-value small {\n font-size: 18px;\n margin-left: 4px;\n }\n</style>\n<div id=\"timer\">\n <Revealer>\n <span slot=\"header\">Timer</span>\n <div class=\"timerContainer\">\n <div class=\"inner\">\n <label>\n <input type=\"range\" bind:value={range} min=1 max=60>\n <button on:click={startTimer} class=\"mui-btn mui-btn--flat mui-btn--dark\">\n {#if timerID === 0}\n Start\n {:else}\n Stop\n {/if}\n </button>\n </label>\n\n </div>\n <div class=\"timer-value\" style=\"color: hsl({120 * (1-$progress)}deg, 50%, 50%) !important\">\n <span>{minutes}mins</span>\n <small>{seconds}s</small>\n </div>\n\n </div>\n </Revealer>\n</div>\n\n",
|
||||||
|
"<script>\n import {onMount} from 'svelte';\n import {format} from 'fecha';\n\n const __url = (__ENV__ === 'production') ? 'https://silvrtree.co.uk/weather' : 'http://localhost:9000/weather';\n\n let weatherData;\n\n onMount(async () => {\n await update();\n });\n\n async function update() {\n await getWeather();\n\n const now = new Date();\n const mod = 1800000 - (now.getTime() % 1800000);\n\n const weatherUpdateFn = function () {\n update();\n };\n\n setTimeout(weatherUpdateFn.bind(this), mod + 10);\n }\n\n function reduceOpenWeather(item) {\n // Openweather returns timestamps in seconds. Moment requires them in milliseconds.\n\n const ts = new Date(item.dt * 1000);\n\n const weatherBlock = item.weather[0];\n\n return {\n 'timestamp': item.dt,\n 'icon': `wi-owm-${weatherBlock.id}`,\n 'summary': weatherBlock.description,\n 'tempHigh': parseInt(item.temp.max, 10),\n 'tempLow': parseInt(item.temp.min, 10),\n 'tempMorn' : parseInt(item.temp.morn, 10),\n 'tempDay' : parseInt(item.temp.day, 10),\n 'tempEve' : parseInt(item.temp.eve, 10),\n 'tempNight' : parseInt(item.temp.night, 10),\n 'datelong': format(ts, 'isoDateTime'),\n 'time': item.dt,\n 'date': format(ts, 'D/M'),\n 'day': format(ts, 'ddd'),\n 'tempHighClass': `temp${parseInt(item.temp.max, 10)}`,\n 'tempLowClass': `temp${parseInt(item.temp.min, 10)}`\n\n };\n }\n\n async function getWeather() {\n\n const res = await fetch(__url);\n const json = await res.json();\n\n if (json) {\n weatherData = json.list.map((item) => {\n // Reduce the data\n return reduceOpenWeather(item);\n });\n }\n }\n\n</script>\n\n<style>\n .card {\n position: relative;\n background-color: #fff;\n min-height: 72px;\n }\n\n .mui--text-display3 {\n font-family: \"Roboto Slab\", \"Helvetica Neue\", Helvetica, Arial;\n }\n\n .temp0, .temp1, .temp2, .temp3, .temp4, .temp5 {\n color: rgb(80, 181, 221)\n }\n\n .temp6 {\n color: rgb(78, 178, 206)\n }\n\n .temp7 {\n color: rgb(76, 176, 190)\n }\n\n .temp8 {\n color: rgb(73, 173, 175)\n }\n\n .temp9 {\n color: rgb(72, 171, 159)\n }\n\n .temp10 {\n color: rgb(70, 168, 142)\n }\n\n .temp11 {\n color: rgb(68, 166, 125)\n }\n\n .temp12 {\n color: rgb(66, 164, 108)\n }\n\n .temp13 {\n color: rgb(102, 173, 94)\n }\n\n .temp14 {\n color: rgb(135, 190, 64)\n }\n\n .temp15 {\n color: rgb(179, 204, 26)\n }\n\n .temp16 {\n color: rgb(214, 213, 28)\n }\n\n .temp17 {\n color: rgb(249, 202, 3)\n }\n\n .temp18 {\n color: rgb(246, 181, 3)\n }\n\n .temp19 {\n color: rgb(244, 150, 26)\n }\n\n .temp20 {\n color: rgb(236, 110, 5)\n }\n\n .day {\n font-family: \"Roboto Slab\", \"Helvetica Neue\", Helvetica, Arial, SansSerif;\n text-transform: uppercase;\n }\n\n .summary::first-letter {\n text-transform: capitalize\n }\n</style>\n\n<div id='weather'>\n\n {#if weatherData}\n {#each weatherData as item}\n <div class=\"card mui--z1 mui-col-md-6 mui-col-lg-4\">\n <div class=\"mui-col-md-3 mui-col-sm-6 mui-col-xs-6\">\n <div class=\"mui--text-accent mui--text-title day mui--text-center\">{item.day}</div>\n <div class=\"mui--text-dark-secondary mui--text-subhead mui--text-center\">{item.date}</div>\n </div>\n <div class=\"mui-col-md-7 mui-col-sm-6 mui-col-xs-6\">\n <div>\n <i class=\"mui--text-headline wi {item.icon }\"></i>\n <span class=\"mui--text-display1 {item.tempHighClass}\">{item.tempHigh}°</span> /\n <span class=\"mui--text-headline {item.tempLowClass}\">{item.tempLow}°</span>\n </div>\n <div class=\"mui--text-caption summary\">{item.summary}</div>\n </div>\n <div class=\"mui-col-md-2 mui--hidden-xs mui--hidden-sm\">\n <div class=\"mui--text-caption\">{item.tempMorn}°</div>\n <div class=\"mui--text-caption\">{item.tempDay}°</div>\n <div class=\"mui--text-caption\">{item.tempEve}°</div>\n <div class=\"mui--text-caption\">{item.tempNight}°</div>\n </div>\n </div>\n {/each}\n {/if}\n\n</div>\n"
|
||||||
|
],
|
||||||
|
"names": [],
|
||||||
|
"mappings": "AAcI,eAAE,CAAC,AACC,OAAO,CAAE,GAAG,AAChB,CAAC,AACD,QAAQ,eAAC,CAAC,MAAM,CAAE,OAAO,AAAC,CAAC,AAC3B,kBAAG,CAAE,oBAAK,CAAC,AACP,QAAQ,YAAY,CACpB,eAAe,CAAE,OAAO,CACxB,MAAM,KAAK,CACX,OAAO,KAAK,AAChB,CAAC,AACD,kBAAG,CAAE,oBAAK,CAAC,AACP,gBAAgB,CAAE,IAAI,2PAA2P,CAAC,AACtR,CAAC,AACD,kBAAG,CAAC,AACA,gBAAgB,CAAE,IAAI,2PAA2P,CAAC,AACtR,CAAC;ACsBD,cAAE,CAAC,AACC,SAAS,CAAE,GAAG,AAClB,CAAC;ACiEJ,SAAS,eAAC,CAAC,AACP,OAAO,GAAG,CAAC,MAAM,CAAC,MAAM,AAC5B,CAAC;AC1DE,eAAe,8BAAC,CAAC,AACb,MAAM,CAAE,GAAG,CAAC,KAAK,CAAC,OAAO,CACzB,gBAAgB,CAAE,OAAO,AAE7B,CAAC,AAED,MAAM,8BAAC,CAAC,AACJ,OAAO,CAAE,IAAI,CAEb,KAAK,CAAE,IAAI,CACX,cAAc,CAAE,MAAM,CACtB,WAAW,CAAE,MAAM,CACnB,eAAe,CAAE,MAAM,AAC3B,CAAC,AAED,YAAY,8BAAC,CAAC,AACV,OAAO,CAAE,IAAI,CAEb,KAAK,CAAE,IAAI,CACX,cAAc,CAAE,GAAG,CACnB,WAAW,CAAE,MAAM,CACnB,eAAe,CAAE,MAAM,CACvB,SAAS,CAAE,IAAI,CACf,MAAM,CAAE,IAAI,CACZ,KAAK,CAAE,IAAI,AACf,CAAC,AAED,2BAAY,CAAC,KAAK,eAAC,CAAC,AAChB,SAAS,CAAE,IAAI,CACf,WAAW,CAAE,GAAG,AACpB,CAAC;ACxBD,KAAK,eAAC,CAAC,AACH,QAAQ,CAAE,QAAQ,CAClB,gBAAgB,CAAE,IAAI,CACtB,UAAU,CAAE,IAAI,AACpB,CAAC,AAED,mBAAmB,eAAC,CAAC,AACjB,WAAW,CAAE,aAAa,CAAC,CAAC,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,AAClE,CAAC,AAED,qBAAM,CAAE,qBAAM,CAAE,qBAAM,CAAE,qBAAM,CAAE,qBAAM,CAAE,MAAM,eAAC,CAAC,AAC5C,KAAK,CAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;IAC5B,CAAC,AAED,MAAM,eAAC,CAAC,AACJ,KAAK,CAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;IAC5B,CAAC,AAED,MAAM,eAAC,CAAC,AACJ,KAAK,CAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;IAC5B,CAAC,AAED,MAAM,eAAC,CAAC,AACJ,KAAK,CAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;IAC5B,CAAC,AAED,MAAM,eAAC,CAAC,AACJ,KAAK,CAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;IAC5B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;IAC5B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;IAC5B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;IAC5B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;IAC5B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;IAC5B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;IAC5B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;IAC5B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;IAC5B,CAAC,AAED,OAAO,eAAC,CAAC,AACL,KAAK,CAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC,AAED,IAAI,eAAC,CAAC,AACF,WAAW,CAAE,aAAa,CAAC,CAAC,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,CACzE,cAAc,CAAE,SAAS,AAC7B,CAAC,AAED,uBAAQ,cAAc,AAAC,CAAC,AACpB,cAAc,CAAE,UAAU;IAC9B,CAAC"
|
||||||
|
}
|
2
public/build/bundle.js
Executable file
2
public/build/bundle.js
Executable file
File diff suppressed because one or more lines are too long
1
public/build/bundle.js.map
Executable file
1
public/build/bundle.js.map
Executable file
File diff suppressed because one or more lines are too long
BIN
public/favicon.png
Executable file
BIN
public/favicon.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
6
public/gfx/bin.svg
Normal file
6
public/gfx/bin.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" version="1.1" viewBox="0 0 1000 1000" xml:space="preserve">
|
||||||
|
<path d="M322 11a59 59 0 0 0-46 46c-1 9-2 10-8 11-24 3-42 11-56 23-8 7-17 21-20 31l-1 4h496l5 5c8 8 5 20-6 24-3 1-90 2-265 2H162v46h33l1 8a238013 238013 0 0 1 78 777c2 2 279 3 279 1l-10-12c-10-12-21-32-26-49-3-15-3-43 0-58 8-31 33-64 60-77 23-13 31-15 60-15 19 0 25 0 25-2 0-10 50-498 51-499l58-46c42-33 58-47 61-53 6-11 8-26 3-39-2-9-6-13-31-38s-29-28-38-31c-10-3-28-3-161-3H455v-7c0-3-2-12-6-18-7-13-19-24-32-29-8-3-83-4-95-2zm84 39c7 3 11 8 11 14 0 2-6 3-52 3-48 0-52 0-52-4 1-4 6-11 12-13 8-3 73-3 81 0zm396 91c17 9 9 35-10 35-3 0-8-3-12-6-5-4-6-6-6-13s1-9 6-14c7-6 14-7 22-2zM483 251c6 5 6 13 1 19l-4 5h-56c-50 1-58 0-62-2-6-4-8-10-6-17 3-9 4-9 65-9 57 0 58 0 62 4z"/>
|
||||||
|
<path fill="#ff0000"
|
||||||
|
d="M615 811c-32 8-58 33-67 65a90 90 0 0 0 175 45c4-13 3-36-1-49a91 91 0 0 0-107-61zm34 61c11 6 17 14 17 27 0 30-38 41-56 16-10-13-4-35 12-43 10-6 16-6 27 0z"/>
|
||||||
|
</svg>
|
||||||
|
|
After Width: | Height: | Size: 993 B |
BIN
public/gfx/popout.png
Executable file
BIN
public/gfx/popout.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 111 B |
1967
public/global.css
Executable file
1967
public/global.css
Executable file
File diff suppressed because it is too large
Load Diff
21
public/index.html
Executable file
21
public/index.html
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||||
|
|
||||||
|
<title>Slack</title>
|
||||||
|
|
||||||
|
<link rel='icon' type='image/png' href='/favicon.png'>
|
||||||
|
<link href="//fonts.googleapis.com/css2?family=Roboto+Slab&display=swap" rel="stylesheet">
|
||||||
|
<link href="//cdnjs.cloudflare.com/ajax/libs/weather-icons/2.0.9/css/weather-icons.min.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<link rel='stylesheet' href='/global.css'>
|
||||||
|
|
||||||
|
<link rel='stylesheet' href='/build/bundle.css'>
|
||||||
|
|
||||||
|
<script defer src='/build/bundle.js'></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
public/stuff/bell.mp3
Executable file
BIN
public/stuff/bell.mp3
Executable file
Binary file not shown.
81
rollup.config.js
Executable file
81
rollup.config.js
Executable file
@ -0,0 +1,81 @@
|
|||||||
|
import svelte from 'rollup-plugin-svelte';
|
||||||
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
|
import livereload from 'rollup-plugin-livereload';
|
||||||
|
import { terser } from 'rollup-plugin-terser';
|
||||||
|
import replace from 'rollup-plugin-replace';
|
||||||
|
import css from 'rollup-plugin-css-only';
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
|
function serve() {
|
||||||
|
let server;
|
||||||
|
|
||||||
|
function toExit() {
|
||||||
|
if (server) server.kill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
writeBundle() {
|
||||||
|
if (server) return;
|
||||||
|
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
|
||||||
|
'stdio': ['ignore', 'inherit', 'inherit'],
|
||||||
|
'shell': true
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGTERM', toExit);
|
||||||
|
process.on('exit', toExit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'input': 'src/main.js',
|
||||||
|
'output': {
|
||||||
|
'sourcemap': true,
|
||||||
|
'format': 'iife',
|
||||||
|
'name': 'app',
|
||||||
|
'file': 'public/build/bundle.js'
|
||||||
|
},
|
||||||
|
'plugins': [
|
||||||
|
svelte({
|
||||||
|
'compilerOptions': {
|
||||||
|
// enable run-time checks when not in production
|
||||||
|
'dev': !production
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
// we'll extract any component CSS out into
|
||||||
|
// a separate file - better for performance
|
||||||
|
css({ 'output': 'bundle.css' }),
|
||||||
|
|
||||||
|
// If you have external dependencies installed from
|
||||||
|
// npm, you'll most likely need these plugins. In
|
||||||
|
// some cases you'll need additional configuration -
|
||||||
|
// consult the documentation for details:
|
||||||
|
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
||||||
|
resolve({
|
||||||
|
'browser': true,
|
||||||
|
'dedupe': ['svelte']
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
replace({
|
||||||
|
'exclude': 'node_modules/**',
|
||||||
|
'__ENV__': JSON.stringify(production ? 'production' : 'development')
|
||||||
|
}),
|
||||||
|
|
||||||
|
// In dev mode, call `npm run start` once
|
||||||
|
// the bundle has been generated
|
||||||
|
!production && serve(),
|
||||||
|
|
||||||
|
// Watch the `public` directory and refresh the
|
||||||
|
// browser on changes when not in production
|
||||||
|
!production && livereload('public'),
|
||||||
|
|
||||||
|
// If we're building for production (npm run build
|
||||||
|
// instead of npm run dev), minify
|
||||||
|
production && terser()
|
||||||
|
],
|
||||||
|
'watch': {
|
||||||
|
'clearScreen': false
|
||||||
|
}
|
||||||
|
};
|
396
src/App.svelte
Executable file
396
src/App.svelte
Executable file
@ -0,0 +1,396 @@
|
|||||||
|
<script>
|
||||||
|
import Panel from './components/Panel.svelte';
|
||||||
|
import Header from './components/Header.svelte';
|
||||||
|
import Events from './components/Events.svelte';
|
||||||
|
import Password from './components/Password.svelte';
|
||||||
|
import Timer from './components/Timer.svelte';
|
||||||
|
|
||||||
|
export let events = [];
|
||||||
|
|
||||||
|
function popitoutSmall(e) {
|
||||||
|
if (e.target.dataset.url) {
|
||||||
|
const newwindow = window.open(e.target.dataset.url, 'name', 'height=400,width=520');
|
||||||
|
if (window.focus) {
|
||||||
|
newwindow.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function popitout(e) {
|
||||||
|
if (e.target.dataset.url) {
|
||||||
|
const newwindow = window.open(e.target.dataset.url, 'name', 'height=600,width=570');
|
||||||
|
if (window.focus) {
|
||||||
|
newwindow.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<Header/>
|
||||||
|
<div class="mui-container">
|
||||||
|
<Events events="{events}"/>
|
||||||
|
|
||||||
|
<div class="mui-panel">
|
||||||
|
<div class="mui-row">
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Admin</div>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://silvrtree.co.uk/slack">Slack Home</a></li>
|
||||||
|
<li><a href="https://beeksfinancialcloud-6bb2c0866841d5.sharepoint.com/Time-OffManagerPro/Pages/Default.aspx?SPHostUrl=https%3A%2F%2Fbeeksfinancialcloud%2Esharepoint%2Ecom&SPLanguage=en%2DUS&SPClientTag=0&SPProductNumber=16%2E0%2E21910%2E12005&SPAppWebUrl=https%3A%2F%2Fbeeksfinancialcloud%2D6bb2c0866841d5%2Esharepoint%2Ecom%2FTime%2DOffManagerPro">Time-Off Manager Pro</a></li>
|
||||||
|
|
||||||
|
<li><a href="https://beeksfinancialcloud.sharepoint.com/SitePages/Home.aspx">Beeks Sharepoint</a></li>
|
||||||
|
<li><a href="https://app.hibob.com/home">Bob</a></li>
|
||||||
|
<li><a href="https://beeksanalytics.atlassian.net/wiki/spaces/WD/pages/1775435799/Sprint+Ceremonies+Documents">Sprint Ceremonies Docs</a></li>
|
||||||
|
<li><a href="https://miro.com/app/board/uXjVOZ4OdB8=/">Retrospective Board - Miro</a></li>
|
||||||
|
</ul>
|
||||||
|
</Panel>
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Tools</div>
|
||||||
|
<ul>
|
||||||
|
<li><a href='/cleaner'>Cleaner</a></li>
|
||||||
|
<li><a href='https://kanbanflow.com'>Kanban Flow</a></li>
|
||||||
|
<li><a href="https://www.linode.com/">Linode</a></li>
|
||||||
|
<li><a href="http://www.colorzilla.com/gradient-editor/">CSS Gradient Generator</a></li>
|
||||||
|
<li><a href="http://utilities-online.info/xmltojson">XML to JSON</a></li>
|
||||||
|
<li><a href="http://shancarter.com/data_converter">CSV to JSON</a></li>
|
||||||
|
<li><a href="http://cubic-bezier.com/">Cubic Bezier</a></li>
|
||||||
|
<li><a href="http://gskinner.com/RegExr/">RegEx Tool</a></li>
|
||||||
|
<li><a href="http://closure-compiler.appspot.com/home">Closure Compiler</a></li>
|
||||||
|
<li><a href="http://jsonlint.com/">JSON Lint</a></li>
|
||||||
|
<li><a href="http://jsoneditoronline.org/">JSON Editor</a></li>
|
||||||
|
<li><a href="http://www.base64decode.org/">Base64 Decoder</a></li>
|
||||||
|
<li><a href="http://jsbeautifier.org/">JS Beautifier</a></li>
|
||||||
|
<li><a href="http://spritepad.wearekiss.com/">Spritepad</a></li>
|
||||||
|
<li><a href="http://draeton.github.com/stitches/">Sprite Sheet Generator</a></li>
|
||||||
|
<li><a href="http://www.cleancss.com/">CSS Optimizer</a></li>
|
||||||
|
<li><a href="http://fontello.com/">Icon Font Generator</a></li>
|
||||||
|
<li><a href="http://html2jade.aaron-powell.com/">HTML to Jade</a></li>
|
||||||
|
<li><a href="http://cdnjs.com//">Cloudflare JS CDN</a></li>
|
||||||
|
<li><a href="http://www.willpeavy.com/minifier/">HTML Minifier</a></li>
|
||||||
|
<li><a href='https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet'>XSS Cheat Sheet</a></li>
|
||||||
|
<li><a href='http://jsfiddle.net/'>JSFiddle</a></li>
|
||||||
|
<li><a href="http://jsbin.com/">JS Bin</a></li>
|
||||||
|
<li><a href='https://draftin.com/documents'>Draftin</a></li>
|
||||||
|
<li><a href="https://romannurik.github.io/AndroidAssetStudio/icons-launcher.html">Android Asset</a></li>
|
||||||
|
<li><a href="https://xkpasswd.net/s/">Password Generator</a></li>
|
||||||
|
<li><a href="https://howsecureismypassword.net/">Password Checker</a></li>
|
||||||
|
<li><a href="https://archive.today">Archive Today</a></li>
|
||||||
|
<li><a href="http://staticmapmaker.com/google/">Static Map Generator</a></li>
|
||||||
|
<li><a href="https://httpbin.org/">AJAX Endpoints</a></li>
|
||||||
|
<li><a href="https://tools.bartlweb.net/webssh/">WebSSH</a></li>
|
||||||
|
<li><a href="http://jade-lang.com/demo/">Jade Tester</a></li>
|
||||||
|
<li><a href="https://es6console.com/">ES6 Console</a></li>
|
||||||
|
<li><a href="https://crontab.guru/">Cron Guru</a></li>
|
||||||
|
<li><a href="https://fontdrop.info/">FontDrop</a></li>
|
||||||
|
<li><a href="https://ondras.zarovi.cz/sql/demo/">SQLDesigner</a></li>
|
||||||
|
<li><a href="http://www.databaseanswers.org/data_models/index.htm">Database Models</a></li>
|
||||||
|
<li><a href="https://www.typescriptlang.org/play?#">Typescript Play</a></li>
|
||||||
|
</ul>
|
||||||
|
<Password/>
|
||||||
|
<Timer/>
|
||||||
|
</Panel>
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Bitcoin
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://www.bitstamp.net">Bitstamp</a></li>
|
||||||
|
<li><a href="https://www.kraken.net">Kraken</a></li>
|
||||||
|
<li><a href="https://cryptowat.ch/">Cryptowat.ch</a></li>
|
||||||
|
<li><a href="http://www.coindesk.com/price/">BTC Chart</a></li>
|
||||||
|
<li><a href="https://bitcoinwisdom.com/">BTC Chart 2</a></li>
|
||||||
|
<li><a href="http://bitcoinity.org/markets/bitstamp/USD">BitStamp Chart</a></li>
|
||||||
|
<li><a href="http://btc-chart.com/market/bitstamp/86400">Bitstamp Chart 2</a></li>
|
||||||
|
<li><a href="https://bitbargain.co.uk">BitBargin UK</a></li>
|
||||||
|
<li><a href="https://yacuna.com/">Yacuna UK</a></li>
|
||||||
|
<li><a href="http://blockchain.info/">Blockchain</a></li>
|
||||||
|
<li><a href="http://bitminter.com/">Bitminter</a></li>
|
||||||
|
<li><a href="http://preev.com/">BTC Exchange Rate</a></li>
|
||||||
|
<li><a href="http://www.silvrtree.co.uk/watch.html">CFT Watcher</a>
|
||||||
|
<span style="cursor: pointer;" data-url="http://www.silvrtree.co.uk/watch.html">
|
||||||
|
<img on:click|stopPropagation={popitoutSmall} src="gfx/popout.png" alt="CFT Watcher"
|
||||||
|
data-url="http://www.silvrtree.co.uk/watch.html">
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mui-row">
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Package Tracking</div>
|
||||||
|
<!-- Computer News -->
|
||||||
|
<ul>
|
||||||
|
<li><a href="http://m.ups.com/">UPS</a></li>
|
||||||
|
</ul>
|
||||||
|
</Panel>
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Weather</div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.accuweather.com/ukie/index-forecast.asp?postalcode=G82%201RG">Dumbarton
|
||||||
|
Weather</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.wunderground.com/cgi-bin/findweather/getForecast?query=dumbarton,%20uk&wuSelect=WEATHER">WU
|
||||||
|
Dumbarton Weather</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://weather.yahoo.com/forecast/UKXX0663.html?unit=c">Y! Dumbarton Weather</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.accuweather.com/ukie/index-forecast.asp?postalcode=G9%202SU">Glasgow
|
||||||
|
Weather</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.wunderground.com/cgi-bin/findweather/getForecast?query=glasgow,%20uk&wuSelect=WEATHER">WU
|
||||||
|
Glasgow Weather</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.nowcast.co.uk/lightning/">Live Lightning</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.upminsterweather.co.uk/test/live_lightning.htm">Other Live Lightning</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.meteorologica.info/freedata_lightning.htm">Closer Live Lightning</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.malvernwx.co.uk/lightning_data/lightning.htm">Multiple Lightning</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.blitzortung.org/Webpages/index.php">European Lightning</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.madpaddler.net/wxlightning.php">East Kilbride Lightning</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.bordersweather.co.uk/wxlightning.php">Borders Lightning</a>
|
||||||
|
</li>
|
||||||
|
<li><a href='https://www.lightningmaps.org/#m=oss;t=3;s=0;o=0;b=;ts=0;y=56.6056;x=-4.1777;z=8;d=2;dl=2;dc=0;'>Best
|
||||||
|
Live Lightning</a></li>
|
||||||
|
<li><a href="http://www.madpaddler.net/wxais.php">Ships</a></li>
|
||||||
|
<li><a href='http://www.raintoday.co.uk/'>Rain Today</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</Panel>
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Free Email WEBpages</div>
|
||||||
|
<!-- Free Email WEBpages -->
|
||||||
|
<ul>
|
||||||
|
<li><a href="http://gmail.google.com/">Gmail</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.unmajestic.com/webmail/">Unmajestic Webmail</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.artizanconsulting.co.uk/webmail/">Artizan Webmail</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://mail.yahoo.com">Yahoo Mail</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.guerrillamail.com/">Guerrilla Mail Anti Spam</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="mui-row">
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Contracting</div>
|
||||||
|
<ul>
|
||||||
|
<li><a href='https://outsauce.backofficeportal.com/Secure/Candidate/Default.aspx'>Outsauce Timesheets</a></li>
|
||||||
|
<li><a href='https://worksheets.computerfutures.com/'>CF Timesheets</a></li>
|
||||||
|
<li><a href="http://www.monster.co.uk/">monster</a></li>
|
||||||
|
<li><a href="http://www.cwjobs.co.uk/">cwjobs</a></li>
|
||||||
|
<li><a href="http://www.s1jobs.com/myaccount/">s1jobs</a></li>
|
||||||
|
<li><a href="http://www.jobserve.com/">jobserve</a></li>
|
||||||
|
<li><a href="http://www.jobsite.co.uk/jbe/myprofile/">jobsite</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.itjobswatch.co.uk/contracts/scotland/asp.do">IT Jobs Watch Scotland</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Entertainment</div>
|
||||||
|
<!-- Entertainment -->
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="http://genre.amazingradio.co.uk:8000/stream.mp3?arplayer=1">Amazing Radio Chill</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.cineworld.co.uk/cinemas/28?fallback=false&isMobileAgent=false">Cineworld</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.showcasecinemas.co.uk/showtimes/default.asp?selectTheatre=8508">Showcase</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.imdb.com/">Imdb</a></li>
|
||||||
|
<li><a href="http://www.epguides.com/">EPGuides</a></li>
|
||||||
|
<li><a href="http://eztv.it">Eztv</a></li>
|
||||||
|
<li><a href="http://www.mininova.org">Mininova</a></li>
|
||||||
|
<li><a href="http://www.scrapetorrent.com">Scrapetorrent</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="http://glasgow.myvillage.com/events">Whats on In Glasgow</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.5pm.co.uk/Search/Event/">Local Events</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://necta.jansenit.com:8000/necta192.mp3">Nectarine</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="/playlists/str.pls">STR - Space Travel Radio</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="/playlists/musik.drumstep.pls">musik.drumstep</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Travel
|
||||||
|
</div>
|
||||||
|
<!-- Travel -->
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href='http://www.journeycheck.com/firstscotrail'>Journey Check</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.virgintrainseastcoast.com/trainmapper/">Train Mapper</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://trafficscotland.org/whatsnearme/#">Traffic Scotland</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.theaa.com/traffic-news/glasgow%20uk/">AA Traffic News</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://ojp.nationalrail.co.uk/service/ldbboard/dep/DBE/WES/To?ar=true">DBE->WES</a>
|
||||||
|
/
|
||||||
|
<a href="http://www.traintime.uk/index.php?view=desktop&from=DBE&to=WES">Advanced</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://ojp.nationalrail.co.uk/service/ldbboard/dep/WES/DBE/To?ar=true">WES->DBE</a>
|
||||||
|
<span style="cursor: pointer;"><img
|
||||||
|
on:click|stopPropagation={popitout} src="gfx/popout.png"
|
||||||
|
data-url="http://ojp.nationalrail.co.uk/service/ldbboard/dep/WES/DBE/To?ar=true#skip-content-hold"></span>
|
||||||
|
/
|
||||||
|
<a href="http://www.traintime.uk/index.php?view=desktop&from=WES&to=DBE">Advanced</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.livedepartureboards.co.uk/ldb/summary.aspx?T=DBE">DBE Board</a>
|
||||||
|
/
|
||||||
|
<a href="http://www.stationboard.uk/index.php?view=desktop&station1=DBE&direction=departures">Advanced</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.livedepartureboards.co.uk/ldb/summary.aspx?T=GLQ">GLQ Trains</a> /
|
||||||
|
<a href="http://www.stationboard.uk/index.php?view=desktop&station1=GLQ&direction=departures">Adv</a> /
|
||||||
|
<a href="http://www.traintime.uk/index.php?view=desktop&from=GLQ&to=DBE">GLQ->DBE</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.kayak.co.uk/">Kayak</a></li>
|
||||||
|
<li><a href="http://www.travelocity.co.uk/">Travelocity</a></li>
|
||||||
|
<li><a href="http://www.travel.com/sitemap.htm">Travel.com</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.landings.com/_landings/pages/commercial.html">Airlines</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.flightstats.com">Landings</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.lib.utexas.edu/Libs/PCL/Map_collection/map_sites/map_sites.html#general">Maps</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.sitesatlas.com/Maps/">Maps2</a></li>
|
||||||
|
<li><a href="http://www.itn.net/">ITN</a></li>
|
||||||
|
<li><a href="http://bahn.hafas.de/bin/query.exe/en">HAFAS</a></li>
|
||||||
|
<li><a href="http://bahn.hafas.de/bin/query.exe/en">DieBahn</a></li>
|
||||||
|
<li><a href="http://www.cwrr.com/nmra/travelreg.html">RailUSA</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.trainweb.com/frames_travel.html">TrainWeb</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.cwrr.com/nmra/travelw2.html">RailWorld</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.xe.net/currency/">Currency Converter</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.cia.gov/cia/publications/factbook/index.html">CIA</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://maps.google.com/">GMaps</a></li>
|
||||||
|
<li><a href='https://unop.uk/tube/'>Tube Status</a></li>
|
||||||
|
</ul>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mui-row">
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Computer Software, etc.</div>
|
||||||
|
<ul>
|
||||||
|
<li><a href="">Portable Apps</a></li>
|
||||||
|
<li><a href="http://www.newfreeware.com/">NewFreeware</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.makeuseof.com/tag/portable-software-usb/">Portable Software</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.portablefreeware.com/">Portable Freeware Collection</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Reference & Special sites</div>
|
||||||
|
<!-- Reference and Special sites -->
|
||||||
|
<ul>
|
||||||
|
<li><a href="http://www.glossarist.com/default.asp">Glossaries</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.convert-me.com/en/">Converters</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="http://decoder.americom.com/cgi-bin/decoder.cgi">DECODE</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.rxlist.com/">Drugs</a></li>
|
||||||
|
<li><a href="http://www.ncbi.nlm.nih.gov/PubMed/">Medline</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.logos.it/dictionary/owa/sp?lg=EN">Translation</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://www.onelook.com/">One Look</a></li>
|
||||||
|
<li><a href="http://www.defenselink.mil/">US Military</a></li>
|
||||||
|
<li><a href="http://www.fedworld.gov/">US Fed</a></li>
|
||||||
|
<li><a href="http://www.ih2000.net/ira/legal.htm">Legal</a></li>
|
||||||
|
<li><a href="http://www.nih.gov/">NIH</a></li>
|
||||||
|
<li><a href="http://www.refdesk.com/">RefDESK</a></li>
|
||||||
|
<li><a href="http://www.britannica.com/">Britannica</a></li>
|
||||||
|
<li><a href="http://www.capitolimpact.com/gw/">States</a></li>
|
||||||
|
<li><a href="http://www.packtrack.com/">PackTrack</a></li>
|
||||||
|
<li><a href="http://www.acronymfinder.com/">Acronym</a></li>
|
||||||
|
<li><a href="http://www.visualthesaurus.com/">V-Thes</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.timelineindex.com/content/home/forced">Timelines</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://en.wikipedia.org/wiki/Main_Page">Wikipedia</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
<div class="mui--text-title mui-text-black">Earth and Beyond</div>
|
||||||
|
<!-- Good Reading Misc. -->
|
||||||
|
<ul>
|
||||||
|
<li><a href="http://enbarsenal.com">ENB Arsenal</a></li>
|
||||||
|
<li><a href="http://enb.wikia.com/">ENB Wikia</a></li>
|
||||||
|
<li><a href="http://enb.gearlist.co.uk/">Gear List</a></li>
|
||||||
|
<li><a href='http://forum.enb-emulator.com/'>Emu Forum</a></li>
|
||||||
|
<li><a href="http://net-7.org/wiki/index.php?title=Main_Page">Net 7 Wiki</a></li>
|
||||||
|
<li><a href="http://spaceengineers.wikia.com/wiki/Space_Engineers_Wiki">Space Engineers Wiki</a></li>
|
||||||
|
<li><a href="http://forums.keenswh.com/">Space Engineers Forum</a></li>
|
||||||
|
</ul>
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
71
src/components/Bins.svelte
Normal file
71
src/components/Bins.svelte
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<script>
|
||||||
|
let todayStr;
|
||||||
|
let tomorrowStr;
|
||||||
|
let outString;
|
||||||
|
|
||||||
|
let binIcon = function (iconColour) {
|
||||||
|
return `<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="18" version="1.1" viewBox="0 0 1000 1000" xml:space="preserve">
|
||||||
|
<path fill="${iconColour}" d="M322 11a59 59 0 0 0-46 46c-1 9-2 10-8 11-24 3-42 11-56 23-8 7-17 21-20 31l-1 4h496l5 5c8 8 5 20-6 24-3 1-90 2-265 2H162v46h33l1 8a238013 238013 0 0 1 78 777c2 2 279 3 279 1l-10-12c-10-12-21-32-26-49-3-15-3-43 0-58 8-31 33-64 60-77 23-13 31-15 60-15 19 0 25 0 25-2 0-10 50-498 51-499l58-46c42-33 58-47 61-53 6-11 8-26 3-39-2-9-6-13-31-38s-29-28-38-31c-10-3-28-3-161-3H455v-7c0-3-2-12-6-18-7-13-19-24-32-29-8-3-83-4-95-2zm84 39c7 3 11 8 11 14 0 2-6 3-52 3-48 0-52 0-52-4 1-4 6-11 12-13 8-3 73-3 81 0zm396 91c17 9 9 35-10 35-3 0-8-3-12-6-5-4-6-6-6-13s1-9 6-14c7-6 14-7 22-2zM483 251c6 5 6 13 1 19l-4 5h-56c-50 1-58 0-62-2-6-4-8-10-6-17 3-9 4-9 65-9 57 0 58 0 62 4z"/>
|
||||||
|
<path fill="${iconColour}"
|
||||||
|
d="M615 811c-32 8-58 33-67 65a90 90 0 0 0 175 45c4-13 3-36-1-49a91 91 0 0 0-107-61zm34 61c11 6 17 14 17 27 0 30-38 41-56 16-10-13-4-35 12-43 10-6 16-6 27 0z"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
$: {
|
||||||
|
const today = new Date();
|
||||||
|
|
||||||
|
todayStr = calcDay(today);
|
||||||
|
tomorrowStr = calcDay(today.setDate(today.getDate() + 1));
|
||||||
|
|
||||||
|
outString = todayStr.length > 0 ? `Today: ${todayStr}` : "";
|
||||||
|
|
||||||
|
if (tomorrowStr.length > 0) {
|
||||||
|
outString =
|
||||||
|
todayStr.length > 0 ? `${outString}, Tomorrow: ${tomorrowStr}` : `Tomorrow: ${tomorrowStr}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const glassArray = ["0-5", "1-2", "2-2", "2-30", "3-27", "4-25", "5-22", "6-20", "7-17", "8-14", "9-12", "10-9", "11-7",];
|
||||||
|
const blueArray = ["0-7", "0-21", "1-18", "2-4", "3-1", "3-15", "4-13", "4-27", "5-24", "6-8", "7-5", "7-19", "8-16", "8-19", "9-28", "10-11", "11-9", "11-23",];
|
||||||
|
|
||||||
|
const greenArray = ["0-14", "1-25", "3-8", "4-20", "6-1", "7-12", "8-23", "10-4", "11-16",];
|
||||||
|
|
||||||
|
const bothArray = ["1-4", "2-18", "3-29", "5-10", "6-22", "8-2", "9-14", "10-25",];
|
||||||
|
|
||||||
|
function calcDay(d) {
|
||||||
|
|
||||||
|
const n = new Date(d);
|
||||||
|
|
||||||
|
let output = "";
|
||||||
|
|
||||||
|
const day = n.getDay();
|
||||||
|
|
||||||
|
const partString = `${n.getMonth()}-${n.getDate()}`;
|
||||||
|
|
||||||
|
if (day === 3) output = binIcon("#db7e32");
|
||||||
|
|
||||||
|
if (day === 2 && glassArray.indexOf(partString) !== -1) {
|
||||||
|
output = binIcon("#212121");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (day === 4) {
|
||||||
|
if (blueArray.indexOf(partString) !== -1) {
|
||||||
|
output = binIcon("#3535ff");
|
||||||
|
} else if (greenArray.indexOf(partString) !== -1) {
|
||||||
|
output = binIcon("#359235");
|
||||||
|
} else if (bothArray.indexOf(partString) !== -1) {
|
||||||
|
output = `${binIcon("#3535ff")}${binIcon("#359235")}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Bins: {@html outString}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
140
src/components/Bitcoin.svelte
Executable file
140
src/components/Bitcoin.svelte
Executable file
@ -0,0 +1,140 @@
|
|||||||
|
<script>
|
||||||
|
import { get, writable } from 'svelte/store';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
const __url = (__ENV__ === 'production') ? 'https://silvrtree.co.uk' : 'http://localhost:9000';
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
'lastGBP': 0.0,
|
||||||
|
'lastUSD': 0.0,
|
||||||
|
'lows': { 'gbp': 0, 'usd': 0 },
|
||||||
|
'highs': { 'gbp': 0, 'usd': 0 },
|
||||||
|
'eclass': '',
|
||||||
|
'balance': 0.0,
|
||||||
|
'trend': 0
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastUSD = 0, lastGBP, owned, eclass = '', trendClass = '';
|
||||||
|
|
||||||
|
let spread = { 'low':0, 'high':0 };
|
||||||
|
|
||||||
|
const myBTC = 0.02395617;
|
||||||
|
let myWallet = 0.00;
|
||||||
|
|
||||||
|
const btcData = writable(data);
|
||||||
|
const balance = writable(0);
|
||||||
|
|
||||||
|
btcData.subscribe(btcObj => {
|
||||||
|
lastUSD = btcObj.lastUSD;
|
||||||
|
lastGBP = btcObj.lastGBP;
|
||||||
|
eclass = btcObj.eclass;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await update();
|
||||||
|
});
|
||||||
|
|
||||||
|
function calcHighLow(history) {
|
||||||
|
const low = Math.min(...history);
|
||||||
|
const high = Math.max(...history);
|
||||||
|
|
||||||
|
return { low, high };
|
||||||
|
}
|
||||||
|
|
||||||
|
function recalc(freshBTC) {
|
||||||
|
let data = get(btcData);
|
||||||
|
|
||||||
|
data.gbp = freshBTC.bpi.GBP.rate_float;
|
||||||
|
data.usd = freshBTC.bpi.USD.rate_float;
|
||||||
|
data.trend = freshBTC.trend;
|
||||||
|
|
||||||
|
let lastGBP = data.lastGBP;
|
||||||
|
let lastUSD;
|
||||||
|
const g = data.gbp;
|
||||||
|
const u = data.usd;
|
||||||
|
const lows = data.lows;
|
||||||
|
const highs = data.highs;
|
||||||
|
|
||||||
|
spread = calcHighLow(freshBTC.history);
|
||||||
|
|
||||||
|
let eclass = data.eclass;
|
||||||
|
const balance = data.balance;
|
||||||
|
let trend = data.trend;
|
||||||
|
|
||||||
|
if ((trend === undefined) || (trend === null)) trend = 1;
|
||||||
|
|
||||||
|
if (g !== undefined) {
|
||||||
|
if (data.lastGBP !== 0)
|
||||||
|
eclass = g > lastGBP ? 'up' : 'down';
|
||||||
|
else {
|
||||||
|
lows.gbp = g;
|
||||||
|
lows.usd = u;
|
||||||
|
|
||||||
|
highs.gbp = g;
|
||||||
|
highs.usd = u;
|
||||||
|
eclass = '';
|
||||||
|
}
|
||||||
|
lastGBP = g;
|
||||||
|
lastUSD = u;
|
||||||
|
|
||||||
|
if (g < lows.gbp) lows.gbp = g;
|
||||||
|
if (u < lows.usd) lows.usd = u;
|
||||||
|
|
||||||
|
if (highs.gbp < g) highs.gbp = g;
|
||||||
|
if (highs.usd < u) highs.usd = u;
|
||||||
|
|
||||||
|
if (trend > 1.00)
|
||||||
|
trendClass = 'trendUp';
|
||||||
|
else if (trend < 1.00)
|
||||||
|
trendClass = 'trendDown';
|
||||||
|
else
|
||||||
|
trendClass = '';
|
||||||
|
|
||||||
|
myWallet = myBTC * lastGBP;
|
||||||
|
|
||||||
|
data = {
|
||||||
|
lastGBP,
|
||||||
|
lastUSD,
|
||||||
|
lows,
|
||||||
|
highs,
|
||||||
|
eclass,
|
||||||
|
balance,
|
||||||
|
trend
|
||||||
|
};
|
||||||
|
}
|
||||||
|
data.stub = Math.random(Number.MAX_SAFE_INTEGER).toString(32);
|
||||||
|
|
||||||
|
btcData.set(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
await getBtc();
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const mod = 300000 - (now.getTime() % 300000);
|
||||||
|
|
||||||
|
const btcupdateFn = () => {
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(btcupdateFn.bind(this), mod + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getBtc() {
|
||||||
|
const res = await fetch(`${__url}/btc`);
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (json)
|
||||||
|
recalc(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// $${parseFloat(btcdata.lastUSD.toFixed(2)) } / £${parseFloat(btcdata.lastGBP.toFixed(2))} <div>₿${balance} £${parseFloat(owned.toFixed(2))}</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if lastUSD !== 0}
|
||||||
|
<span id="trend" class="{trendClass}" > </span>
|
||||||
|
<span id="btc" class="{eclass}" title="24 Hours: Low: {spread.low.toFixed(2)} / High: {spread.high.toFixed(2)}">${lastUSD.toFixed(2)} / £{lastGBP.toFixed(2)} </span>
|
||||||
|
<span></span>
|
||||||
|
<div class="{eclass}" style="font-size: 85%;">£{myWallet.toFixed(2)}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|
54
src/components/Events.svelte
Executable file
54
src/components/Events.svelte
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import Bins from './Bins.svelte';
|
||||||
|
|
||||||
|
export let events;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
// calculate the dates
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
const now = new Date;
|
||||||
|
const mod = 3600000 - (now.getTime() % 3600000);
|
||||||
|
events = events.map((item) => {
|
||||||
|
item.days = Math.ceil(getDays(now, item.event));
|
||||||
|
item.weeks = Math.ceil(getDays(now, item.event) / 7);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
const clockFn = function () {
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(clockFn.bind(this), mod + 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDays = (startdate, enddate) => {
|
||||||
|
let r, s, e;
|
||||||
|
s = startdate.getTime();
|
||||||
|
e = enddate.getTime();
|
||||||
|
r = (e - s) / (24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="container" class="mui-panel">
|
||||||
|
<div class="mui-row" id="events">
|
||||||
|
{#each events as event}
|
||||||
|
<div class='mui-col-xs-12 mui-col-md-3'>
|
||||||
|
{event.label} {event.days} days / {event.weeks} weeks
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/each}
|
||||||
|
<div class='mui-col-xs-12 mui-col-md-3'>
|
||||||
|
<Bins />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
59
src/components/Fx.svelte
Executable file
59
src/components/Fx.svelte
Executable file
@ -0,0 +1,59 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
const __url = __ENV__ === 'production' ? 'https://silvrtree.co.uk' : 'http://localhost:9000';
|
||||||
|
|
||||||
|
let fxData = {};
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await update();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
await getFX();
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const mod = 1800000 - (now.getTime() % 1800000);
|
||||||
|
|
||||||
|
const fxUpdateFn = () => {
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(fxUpdateFn.bind(this), mod + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reduceFX(data) {
|
||||||
|
if (data.rates !== undefined) {
|
||||||
|
const gpbex = 1 / data.rates.GBP;
|
||||||
|
const sekex = gpbex * data.rates.SEK;
|
||||||
|
fxData = {
|
||||||
|
'usd': 1,
|
||||||
|
'gbp': data.rates.GBP,
|
||||||
|
'sek': data.rates.SEK,
|
||||||
|
'gpbe': gpbex,
|
||||||
|
'sekex': sekex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFX() {
|
||||||
|
const res = await fetch(`${__url}/fx`);
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (json) reduceFX(json);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if fxData.gpbe}
|
||||||
|
<span>
|
||||||
|
£1 = ${parseFloat(fxData.gpbe.toFixed(2))} = {parseFloat(
|
||||||
|
fxData.sekex.toFixed(2)
|
||||||
|
)} SEK
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
</style>
|
5
src/components/Header.svelte
Executable file
5
src/components/Header.svelte
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
<div class="mui-appbar mui--appbar-line-height">
|
||||||
|
<div class="mui-container-fluid">
|
||||||
|
Slack
|
||||||
|
</div>
|
||||||
|
</div>
|
3
src/components/Panel.svelte
Executable file
3
src/components/Panel.svelte
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
<div class="mui-col-md-4">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
78
src/components/Password.svelte
Executable file
78
src/components/Password.svelte
Executable file
@ -0,0 +1,78 @@
|
|||||||
|
<script>
|
||||||
|
import Revealer from './Revealer.svelte';
|
||||||
|
|
||||||
|
Array.prototype.random = function () {
|
||||||
|
return this[Math.floor((Math.random() * this.length))];
|
||||||
|
};
|
||||||
|
|
||||||
|
let longPassword;
|
||||||
|
let shortPassword;
|
||||||
|
|
||||||
|
const alpha = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||||
|
'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
|
'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||||
|
|
||||||
|
const whitespace = ['.', '~', '#', '!', '$', '+', '-', '+'];
|
||||||
|
const numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||||
|
|
||||||
|
const left = ['Alabama', 'Alaska', 'Arizona', 'Maryland', 'Nevada', 'Mexico', 'Texas', 'Utah', 'Glasgow', 'Inverness', 'Edinburgh', 'Dumbarton',
|
||||||
|
'Balloch', 'Renton', 'Cardross', 'Dundee', 'Paisley',
|
||||||
|
'Hamilton', 'Greenock', 'Falkirk',
|
||||||
|
'Irvine',
|
||||||
|
'Renfrew',
|
||||||
|
'Erskine',
|
||||||
|
'London',
|
||||||
|
'Hammersmith',
|
||||||
|
'Islington',
|
||||||
|
'Silver', 'Black', 'Yellow', 'Purple', 'White', 'Pink', 'Red', 'Orange', 'Brown', 'Green', 'Blue', 'Amber', 'Aqua',
|
||||||
|
'Azure', 'Bronze', 'Coral', 'Copper', 'Crimson', 'Cyan', 'Ginger', 'Gold', 'Indigo', 'Jade', 'Ruby', 'Cedar', 'Cream', 'Peach', 'Sepcia',
|
||||||
|
|
||||||
|
'Mercyful', 'Cyber', 'Ultra', 'Hunter', 'Electric', 'Steel', 'Fire', 'Smoke', 'Thunder', 'Pewter', 'Stone', 'Iron', 'Shadow', 'Grey', 'Mocha',
|
||||||
|
'Wood', 'Space', 'Manic', 'Grunt', 'X-Ray', 'Sabbra', 'Atomic'
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
const right = ['Aganju', 'Cygni', 'Akeron', 'Antares', 'Aragoth', 'Ardus', 'Carpenter', 'Cooper', 'Dahin', 'Capella', 'Endriago', 'Gallina',
|
||||||
|
'Fenris', 'Freya', 'Glenn', 'Grissom', 'Jotunheim', 'Kailaasa', 'Lagarto', 'Muspelheim', 'Nifleheim', 'Primus', 'Vega', 'Ragnarok', 'Shepard',
|
||||||
|
'Slayton', 'Tarsis', 'Mercury', 'Venus', 'Mars', 'Earth', 'Terra', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto', 'Europa', 'Ganymede',
|
||||||
|
'Callisto', 'Titan', 'Juno', 'Eridanus', 'Scorpius', 'Crux', 'Cancer', 'Taurus', 'Lyra', 'Andromeda', 'Virgo', 'Aquarius', 'Cygnus', 'Corvus',
|
||||||
|
'Taurus', 'Draco', 'Perseus', 'Pegasus', 'Gemini', 'Columbia', 'Bootes', 'Orion', 'Deneb', 'Merope', 'Agate', 'Amber', 'Beryl', 'Calcite',
|
||||||
|
'Citrine', 'Coral', 'Diamond', 'Emerald', 'Garnet', 'Jade', 'Lapis', 'Moonstone', 'Obsidian', 'Onyx', 'Opal', 'Pearl', 'Quartz', 'Ruby',
|
||||||
|
'Sapphire', 'Topaz', 'Iron', 'Lead', 'Nickel', 'Copper', 'Zinc', 'Tin', 'Manes', 'Argon', 'Neon', 'Alpha', 'Bravo', 'Charlie', 'Delta',
|
||||||
|
'Echo', 'Foxtrot', 'Golf', 'Hotel', 'India', 'Juliett', 'Kilo', 'Lima', 'Mike', 'November', 'Oscar', 'Papa', 'Quebec', 'Romeo', 'Sierra',
|
||||||
|
'Tango', 'Uniform', 'Victor', 'Whisky', 'Xray', 'Yankee', 'Zulu', 'Fate', 'Miner', 'Blaster', 'Click', 'Noise', 'Cadabra', 'Shotgun'];
|
||||||
|
|
||||||
|
function randomAmount(i) {
|
||||||
|
let str = '';
|
||||||
|
|
||||||
|
for (let t = 0; t < i; t++)
|
||||||
|
str = str + alpha.random();
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePassword(e) {
|
||||||
|
// const ws = whitespace.random();
|
||||||
|
const ws = '.';
|
||||||
|
longPassword = (`${left.random() } ${ right.random() } ${ numberCluster()}`).split(' ').join(ws);
|
||||||
|
shortPassword = (`${randomAmount(5) } ${ randomAmount(5)}`).split(' ').join(ws);
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberCluster() {
|
||||||
|
return numbers.random() + numbers.random() + numbers.random();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="passwords">
|
||||||
|
<Revealer>
|
||||||
|
<span slot="header">Generate Password</span>
|
||||||
|
<button on:click={generatePassword} class="mui-btn mui-btn--flat" id='newPassword'>New Password</button>
|
||||||
|
{#if longPassword}
|
||||||
|
<div id='passwordOut' class='password'>
|
||||||
|
<div>Long: {longPassword}</div><div>Short: {shortPassword}</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Revealer>
|
||||||
|
</div>
|
||||||
|
|
40
src/components/Revealer.svelte
Executable file
40
src/components/Revealer.svelte
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
<script>
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
|
let viewMode = 0;
|
||||||
|
let icon;
|
||||||
|
|
||||||
|
$: icon = (viewMode === 0) ? 'up' : 'down';
|
||||||
|
|
||||||
|
function changeViewMode() {
|
||||||
|
viewMode = (viewMode === 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
.up, .down{
|
||||||
|
display:inline-block;
|
||||||
|
background-size: contain;
|
||||||
|
width:0.8em;
|
||||||
|
height:0.8em;
|
||||||
|
}
|
||||||
|
.up, .down{
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' version='1.1' viewBox='0 0 8.4667 8.4667' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m0.82549 1.3138h5.6797l1.136 2e-7 -3.4078 5.8215z' style='stroke-width:.035938;stroke:%23000'/%3E%3C/svg%3E%0A");
|
||||||
|
}
|
||||||
|
.up{
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' version='1.1' viewBox='0 0 8.4667 8.4667' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m1.3226 7.6324v-5.6797l2e-7 -1.136 5.8215 3.4078z' style='stroke-width:.035938;stroke:%23000'/%3E%3C/svg%3E%0A");
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<div><div on:click={changeViewMode} class="{icon}"></div>
|
||||||
|
<span on:click={changeViewMode}><slot name="header">No header was provided</slot></span>
|
||||||
|
</div>
|
||||||
|
{#if viewMode === 1}
|
||||||
|
<div transition:slide>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
{/if}
|
156
src/components/Route.svelte
Executable file
156
src/components/Route.svelte
Executable file
@ -0,0 +1,156 @@
|
|||||||
|
<script>
|
||||||
|
import {route} from './store';
|
||||||
|
|
||||||
|
const __url = (__ENV__ === 'production') ? 'https://traintimes.silvrtree.co.uk' : 'http://localhost:8100';
|
||||||
|
|
||||||
|
let visible = false;
|
||||||
|
let fromStation;
|
||||||
|
let toStation;
|
||||||
|
let url;
|
||||||
|
let routeData = {};
|
||||||
|
let services = [];
|
||||||
|
|
||||||
|
route.subscribe(async (v) => {
|
||||||
|
console.log('>> route', v);
|
||||||
|
fromStation = v.fromStation;
|
||||||
|
toStation = v.toStation;
|
||||||
|
visible = (fromStation !== '') ? !visible : false;
|
||||||
|
|
||||||
|
// url = `https://traintimes.silvrtree.co.uk/gettrains?from=${ fromStation }&to=${ toStation}`;
|
||||||
|
|
||||||
|
url = `${__url}/gettrains?from=${fromStation}&to=${toStation}`;
|
||||||
|
|
||||||
|
if (fromStation !== '' && visible) {
|
||||||
|
await update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function reduceRoute(data) {
|
||||||
|
const newData = {};
|
||||||
|
|
||||||
|
newData.fromName = data.locationName;
|
||||||
|
newData.toName = data.filterLocationName;
|
||||||
|
newData.services = [];
|
||||||
|
|
||||||
|
|
||||||
|
if (typeof data.trainServices === 'object' && data.trainServices !== null)
|
||||||
|
for (const item of data.trainServices) {
|
||||||
|
const dest = item.destination[0];
|
||||||
|
const via = dest.via !== null ? `<em>${dest.via}</em>` : '';
|
||||||
|
const platform = item.platform !== null ? item.platform : '💠';
|
||||||
|
const time = item.sta !== null ? item.sta : `D ${item.std}`;
|
||||||
|
const status = item.eta !== null ? item.eta : item.etd;
|
||||||
|
const cls = (status.toLowerCase() === 'on time') ? 'ontime' : 'delayed';
|
||||||
|
newData.services.push({
|
||||||
|
'location': dest.locationName,
|
||||||
|
'time': time,
|
||||||
|
'via': via,
|
||||||
|
'class': cls,
|
||||||
|
'status': status,
|
||||||
|
'platform': platform,
|
||||||
|
'cancelReason': item.cancelReason,
|
||||||
|
'type': 'train',
|
||||||
|
'isCancelled': item.isCancelled,
|
||||||
|
icon: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.busServices === 'object' && data.busServices !== null)
|
||||||
|
for (const item of data.busServices) {
|
||||||
|
const dest = item.destination[0];
|
||||||
|
const via = dest.via !== null ? `<em>${dest.via}</em>` : '';
|
||||||
|
const platform = item.platform !== null ? item.platform : '';
|
||||||
|
const time = item.sta !== null ? item.sta : `D ${item.std}`;
|
||||||
|
const status = item.eta !== null ? item.eta : item.etd;
|
||||||
|
const cls = (status.toLowerCase() === 'on time') ? 'ontime' : 'delayed';
|
||||||
|
newData.services.push({
|
||||||
|
'location': dest.locationName,
|
||||||
|
'time': time,
|
||||||
|
'via': via,
|
||||||
|
'class': cls,
|
||||||
|
'status': status,
|
||||||
|
'platform': platform,
|
||||||
|
'cancelReason': item.cancelReason,
|
||||||
|
'type': 'bus',
|
||||||
|
'isCancelled': item.isCancelled,
|
||||||
|
icon: '🚌 '
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
routeData = newData;
|
||||||
|
services = newData.services;
|
||||||
|
|
||||||
|
console.log(routeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
await getRoute();
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const mod = 180000 - (now.getTime() % 180000);
|
||||||
|
|
||||||
|
const routeUpdateFn = function () {
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
|
if(visible) setTimeout(routeUpdateFn.bind(this), mod + 10);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getRoute() {
|
||||||
|
console.log('Get route', url);
|
||||||
|
|
||||||
|
const res = await fetch(url);
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (json) {
|
||||||
|
console.log(json);
|
||||||
|
// data = json;
|
||||||
|
reduceRoute(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.routeBox {
|
||||||
|
border:1px dotted silver;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<div class="routeBox">
|
||||||
|
<div>{routeData.fromName} TO {routeData.toName}</div>
|
||||||
|
<table class="mui-table mui-table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Destination</th>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Platform</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{#each services as item}
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>{item.icon}{item.location} {@html item.via}</td>
|
||||||
|
<td class={item.class}>{item.time}</td>
|
||||||
|
{#if !item.isCancelled}
|
||||||
|
<td class={item.class}>{item.status}</td>
|
||||||
|
<td>{item.platform}</td>
|
||||||
|
{:else}
|
||||||
|
<td colspan="2" class="delayed">❌ {item.cancelReason}</td>
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
120
src/components/Timer.svelte
Executable file
120
src/components/Timer.svelte
Executable file
@ -0,0 +1,120 @@
|
|||||||
|
<script>
|
||||||
|
import {tweened} from 'svelte/motion';
|
||||||
|
import Revealer from './Revealer.svelte';
|
||||||
|
|
||||||
|
let timerVisible = false;
|
||||||
|
let range = 25;
|
||||||
|
let start;
|
||||||
|
let timerID = 0;
|
||||||
|
let snd;
|
||||||
|
// let timer = 0;
|
||||||
|
|
||||||
|
$: startVal = range * 60;
|
||||||
|
$: timer = tweened(startVal);
|
||||||
|
|
||||||
|
$: minutes = Math.floor($timer / 60);
|
||||||
|
$: minname = minutes > 1 ? "mins" : "min";
|
||||||
|
$: seconds = Math.floor($timer - minutes * 60)
|
||||||
|
|
||||||
|
const progress = tweened(0, {duration: 1000})
|
||||||
|
$: {
|
||||||
|
$progress = 1 - ($timer / startVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
function complete() {
|
||||||
|
snd = new Audio('stuff/bell.mp3');
|
||||||
|
snd.play();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopTimer() {
|
||||||
|
clearInterval(timerID);
|
||||||
|
timerID = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTimer() {
|
||||||
|
timerVisible = !timerVisible;
|
||||||
|
if (!timerVisible) {
|
||||||
|
// clearTimer
|
||||||
|
stopTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTimer() {
|
||||||
|
|
||||||
|
if (timerID !== 0) {
|
||||||
|
stopTimer();
|
||||||
|
} else {
|
||||||
|
timerID = setInterval(() => {
|
||||||
|
if ($timer > 0) $timer--
|
||||||
|
else {
|
||||||
|
stopTimer();
|
||||||
|
complete();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timerContainer {
|
||||||
|
border: 1px solid #BDBDBD;
|
||||||
|
background-color: #EEEEEE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
display: flex;
|
||||||
|
/*mix-blend-mode: difference;*/
|
||||||
|
color: #333;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-value {
|
||||||
|
display: flex;
|
||||||
|
/*mix-blend-mode: difference;*/
|
||||||
|
color: #333;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 24px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-value small {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="timer">
|
||||||
|
<Revealer>
|
||||||
|
<span slot="header">Timer</span>
|
||||||
|
<div class="timerContainer">
|
||||||
|
<div class="inner">
|
||||||
|
<label>
|
||||||
|
<input type="range" bind:value={range} min=1 max=60>
|
||||||
|
<button on:click={startTimer} class="mui-btn mui-btn--flat mui-btn--dark">
|
||||||
|
{#if timerID === 0}
|
||||||
|
Start
|
||||||
|
{:else}
|
||||||
|
Stop
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="timer-value" style="color: hsl({120 * (1-$progress)}deg, 50%, 50%) !important">
|
||||||
|
<span>{minutes}mins</span>
|
||||||
|
<small>{seconds}s</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Revealer>
|
||||||
|
</div>
|
||||||
|
|
65
src/components/Train.svelte
Executable file
65
src/components/Train.svelte
Executable file
@ -0,0 +1,65 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
import { route } from './store';
|
||||||
|
|
||||||
|
const __url = (__ENV__ === 'production') ? 'https://traintimes.silvrtree.co.uk' : 'http://localhost:8100';
|
||||||
|
|
||||||
|
export let fromStation;
|
||||||
|
export let toStation;
|
||||||
|
let url;
|
||||||
|
let data = { 'eta':'OFF', 'sta': 'OFF' };
|
||||||
|
const display = { 'title':'TRAIN', 'status':'delayed', 'output':'OFF' };
|
||||||
|
|
||||||
|
// $: url = `https://traintimes.silvrtree.co.uk/getnexttraintimes?from=${ fromStation }&to=${ toStation}`;
|
||||||
|
$: {
|
||||||
|
url = `${__url}/getnexttraintimes?from=${ fromStation }&to=${ toStation}`;
|
||||||
|
display.title = `${ fromStation.toUpperCase() }${ toStation.toUpperCase()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recalc stuff
|
||||||
|
$: {
|
||||||
|
display.output = (data.eta.toLowerCase() === 'on time') ? data.sta : data.eta;
|
||||||
|
display.status = (data.eta.toLowerCase() === 'on time') ? 'ontime' : 'delayed';
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await update();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
await getTrain();
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const mod = 120000 - (now.getTime() % 120000);
|
||||||
|
|
||||||
|
const trainUpdateFn = function () {
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(trainUpdateFn.bind(this), mod + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTrain() {
|
||||||
|
const res = await fetch(url);
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (json) {
|
||||||
|
console.log(json);
|
||||||
|
data = json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickHandler() {
|
||||||
|
console.log('click', data);
|
||||||
|
|
||||||
|
route.set({ fromStation, toStation });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div on:click={clickHandler} class='mui-col-xs-12 mui-col-md-6'>{display.title}: <span class="{display.status}">{display.output}</span></div>
|
||||||
|
|
193
src/components/Weather.svelte
Executable file
193
src/components/Weather.svelte
Executable file
@ -0,0 +1,193 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { format } from "fecha";
|
||||||
|
|
||||||
|
const __url =
|
||||||
|
__ENV__ === "production"
|
||||||
|
? "https://silvrtree.co.uk/weather"
|
||||||
|
: "http://localhost:9000/weather";
|
||||||
|
|
||||||
|
let weatherData;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await update();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
await getWeather();
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const mod = 1800000 - (now.getTime() % 1800000);
|
||||||
|
|
||||||
|
const weatherUpdateFn = function () {
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(weatherUpdateFn.bind(this), mod + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reduceOpenWeather(item) {
|
||||||
|
// Openweather returns timestamps in seconds. Moment requires them in milliseconds.
|
||||||
|
|
||||||
|
const ts = new Date(item.dt * 1000);
|
||||||
|
|
||||||
|
const weatherBlock = item.weather[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp: item.dt,
|
||||||
|
icon: `wi-owm-${weatherBlock.id}`,
|
||||||
|
summary: weatherBlock.description,
|
||||||
|
tempHigh: parseInt(item.temp.max, 10),
|
||||||
|
tempLow: parseInt(item.temp.min, 10),
|
||||||
|
tempMorn: parseInt(item.temp.morn, 10),
|
||||||
|
tempDay: parseInt(item.temp.day, 10),
|
||||||
|
tempEve: parseInt(item.temp.eve, 10),
|
||||||
|
tempNight: parseInt(item.temp.night, 10),
|
||||||
|
datelong: format(ts, "isoDateTime"),
|
||||||
|
time: item.dt,
|
||||||
|
date: format(ts, "D/M"),
|
||||||
|
day: format(ts, "ddd"),
|
||||||
|
tempHighClass: `temp${parseInt(item.temp.max, 10)}`,
|
||||||
|
tempLowClass: `temp${parseInt(item.temp.min, 10)}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getWeather() {
|
||||||
|
const res = await fetch(__url);
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (json) {
|
||||||
|
weatherData = json.list.map((item) => {
|
||||||
|
// Reduce the data
|
||||||
|
return reduceOpenWeather(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="weather">
|
||||||
|
{#if weatherData}
|
||||||
|
{#each weatherData as item}
|
||||||
|
<div class="card mui--z1 mui-col-md-6 mui-col-lg-4">
|
||||||
|
<div class="mui-col-md-3 mui-col-sm-6 mui-col-xs-6">
|
||||||
|
<div
|
||||||
|
class="mui--text-accent mui--text-title day mui--text-center"
|
||||||
|
>{item.day}</div>
|
||||||
|
<div
|
||||||
|
class="mui--text-dark-secondary mui--text-subhead mui--text-center"
|
||||||
|
>{item.date}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mui-col-md-7 mui-col-sm-6 mui-col-xs-6">
|
||||||
|
<div>
|
||||||
|
<i class="mui--text-headline wi {item.icon}" />
|
||||||
|
<span class="mui--text-display1 {item.tempHighClass}"
|
||||||
|
>{item.tempHigh}°</span
|
||||||
|
>
|
||||||
|
/
|
||||||
|
<span class="mui--text-headline {item.tempLowClass}"
|
||||||
|
>{item.tempLow}°</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="mui--text-caption summary">{item.summary}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mui-col-md-2 mui--hidden-xs mui--hidden-sm">
|
||||||
|
<div class="mui--text-caption">{item.tempMorn}°</div>
|
||||||
|
<div class="mui--text-caption">{item.tempDay}°</div>
|
||||||
|
<div class="mui--text-caption">{item.tempEve}°</div>
|
||||||
|
<div class="mui--text-caption">{item.tempNight}°</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
background-color: #fff;
|
||||||
|
min-height: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mui--text-display3 {
|
||||||
|
font-family: "Roboto Slab", "Helvetica Neue", Helvetica, Arial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp0,
|
||||||
|
.temp1,
|
||||||
|
.temp2,
|
||||||
|
.temp3,
|
||||||
|
.temp4,
|
||||||
|
.temp5 {
|
||||||
|
color: rgb(80, 181, 221);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp6 {
|
||||||
|
color: rgb(78, 178, 206);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp7 {
|
||||||
|
color: rgb(76, 176, 190);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp8 {
|
||||||
|
color: rgb(73, 173, 175);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp9 {
|
||||||
|
color: rgb(72, 171, 159);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp10 {
|
||||||
|
color: rgb(70, 168, 142);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp11 {
|
||||||
|
color: rgb(68, 166, 125);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp12 {
|
||||||
|
color: rgb(66, 164, 108);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp13 {
|
||||||
|
color: rgb(102, 173, 94);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp14 {
|
||||||
|
color: rgb(135, 190, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp15 {
|
||||||
|
color: rgb(179, 204, 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp16 {
|
||||||
|
color: rgb(214, 213, 28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp17 {
|
||||||
|
color: rgb(249, 202, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp18 {
|
||||||
|
color: rgb(246, 181, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp19 {
|
||||||
|
color: rgb(244, 150, 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp20 {
|
||||||
|
color: rgb(236, 110, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
font-family: "Roboto Slab", "Helvetica Neue", Helvetica, Arial,
|
||||||
|
SansSerif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary::first-letter {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
</style>
|
6
src/components/store.js
Executable file
6
src/components/store.js
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
const route = writable({ 'fromStation':'', 'toStation':'' });
|
||||||
|
|
||||||
|
export { route };
|
||||||
|
|
13
src/main.js
Executable file
13
src/main.js
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
import App from './App.svelte';
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
'target': document.body,
|
||||||
|
'props': {
|
||||||
|
'events' : [
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
Loading…
Reference in New Issue
Block a user