mirror of
https://github.com/Team4388/ScoutingApp2022.git
synced 2026-06-08 16:28:04 -06:00
AAAAAAAAAAAAGH
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"prettier.printWidth": 400,
|
||||
"prettier.semi": false,
|
||||
"prettier.singleQuote": true
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"prettier.configPath": "${workspaceFolder}/.prettierrc.json"
|
||||
"prettier.printWidth": 800
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
# This file contains important security parameters. If you modify this file
|
||||
# manually, Certbot will be unable to automatically provide future security
|
||||
# updates. Instead, Certbot will print and log an error message with a path to
|
||||
# the up-to-date file that you will need to refer to when manually updating
|
||||
# this file.
|
||||
|
||||
ssl_session_cache shared:le_nginx_SSL:10m;
|
||||
ssl_session_timeout 1440m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN DH PARAMETERS-----
|
||||
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
|
||||
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
|
||||
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
|
||||
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
|
||||
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
|
||||
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
|
||||
-----END DH PARAMETERS-----
|
||||
+26
-4
@@ -2,7 +2,7 @@ version: "3"
|
||||
|
||||
services:
|
||||
scouting-webserver-prod:
|
||||
container_name: "scouting-webserver-production"
|
||||
container_name: "production"
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
@@ -10,9 +10,23 @@ services:
|
||||
context: ./webserver
|
||||
dockerfile: ./Dockerfile.prod
|
||||
expose:
|
||||
- 80
|
||||
- 8080
|
||||
ports:
|
||||
- "80:80/tcp"
|
||||
- "8080:8080/tcp"
|
||||
# - "80:80/tcp"
|
||||
# - "443:443/tcp"
|
||||
volumes:
|
||||
- ./webserver/nginx:/etc/nginx/:ro
|
||||
ssl-proxy:
|
||||
image: fsouza/docker-ssl-proxy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
- DOMAIN=10.43.88.1
|
||||
- TARGET_PORT=8080
|
||||
- TARGET_HOST=scouting-webserver-prod
|
||||
- SSL_PORT:443
|
||||
couchdb:
|
||||
container_name: "scouting-couchdb"
|
||||
restart: unless-stopped
|
||||
@@ -33,4 +47,12 @@ services:
|
||||
volumes:
|
||||
- ./couchdb/db.local.ini:/opt/couchdb/etc/local.ini
|
||||
- ./couchdb/data:/opt/couchdb/data
|
||||
|
||||
couch-ssl-proxy:
|
||||
image: fsouza/docker-ssl-proxy
|
||||
ports:
|
||||
- "5985:5985"
|
||||
environment:
|
||||
- DOMAIN=10.43.88.1
|
||||
- TARGET_PORT=5984
|
||||
- TARGET_HOST=scouting-couchdb
|
||||
- SSL_PORT:5985
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
docker-compose run certonly --webroot --webroot-path /var/www/certbot/ --dry-run -d scouting.local
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
|
||||
if ! [ -x "$(command -v docker-compose)" ]; then
|
||||
echo 'Error: docker-compose is not installed.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
domains=scouting.local
|
||||
rsa_key_size=4096
|
||||
data_path="./data/certbot"
|
||||
email="" # Adding a valid address is strongly recommended
|
||||
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits
|
||||
|
||||
if [ -d "$data_path" ]; then
|
||||
read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
|
||||
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
|
||||
echo "### Downloading recommended TLS parameters ..."
|
||||
mkdir -p "$data_path/conf"
|
||||
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
|
||||
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
|
||||
echo
|
||||
fi
|
||||
|
||||
echo "### Creating dummy certificate for $domains ..."
|
||||
path="/etc/letsencrypt/live/$domains"
|
||||
mkdir -p "$data_path/conf/live/$domains"
|
||||
docker-compose run --rm --entrypoint "\
|
||||
openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\
|
||||
-keyout '$path/privkey.pem' \
|
||||
-out '$path/fullchain.pem' \
|
||||
-subj '/CN=localhost'" certbot
|
||||
echo
|
||||
|
||||
|
||||
echo "### Starting nginx ..."
|
||||
docker-compose up --force-recreate -d nginx
|
||||
echo
|
||||
|
||||
echo "### Deleting dummy certificate for $domains ..."
|
||||
docker-compose run --rm --entrypoint "\
|
||||
rm -Rf /etc/letsencrypt/live/$domains && \
|
||||
rm -Rf /etc/letsencrypt/archive/$domains && \
|
||||
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
|
||||
echo
|
||||
|
||||
|
||||
echo "### Requesting Let's Encrypt certificate for $domains ..."
|
||||
#Join $domains to -d args
|
||||
domain_args=""
|
||||
for domain in "${domains[@]}"; do
|
||||
domain_args="$domain_args -d $domain"
|
||||
done
|
||||
|
||||
# Select appropriate email arg
|
||||
case "$email" in
|
||||
"") email_arg="--register-unsafely-without-email" ;;
|
||||
*) email_arg="--email $email" ;;
|
||||
esac
|
||||
|
||||
# Enable staging mode if needed
|
||||
if [ $staging != "0" ]; then staging_arg="--staging"; fi
|
||||
|
||||
docker-compose run --rm --entrypoint "\
|
||||
certbot certonly --webroot -w /var/www/certbot \
|
||||
$staging_arg \
|
||||
$email_arg \
|
||||
$domain_args \
|
||||
--rsa-key-size $rsa_key_size \
|
||||
--agree-tos \
|
||||
--force-renewal" certbot
|
||||
echo
|
||||
|
||||
echo "### Reloading nginx ..."
|
||||
docker-compose exec nginx nginx -s reload
|
||||
@@ -1,9 +1,12 @@
|
||||
FROM node:16-alpine as build-step
|
||||
RUN mkdir /app/
|
||||
RUN mkdir /app/node_modules
|
||||
RUN chown -R node:node /app/node_modules
|
||||
#RUN mkdir /app/node_module/.bin
|
||||
WORKDIR /app/
|
||||
ENV PATH /app/node_modules/.bin:$PATH
|
||||
RUN chown -R node:node /app/node_modules
|
||||
COPY package*.json ./
|
||||
COPY package.json ./
|
||||
COPY package-lock.json ./
|
||||
RUN npm install
|
||||
COPY . ./
|
||||
# USER node
|
||||
@@ -12,6 +15,7 @@ RUN npm run build
|
||||
FROM nginx:1-alpine
|
||||
COPY --from=build-step /app/build /usr/share/nginx/html
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY nginx/nginx.conf /etc/nginx/conf.d
|
||||
EXPOSE 80
|
||||
# COPY nginx/nginx.conf /etc/nginx/conf.d
|
||||
EXPOSE 8080
|
||||
# EXPOSE 443
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
server {
|
||||
listen 8080;
|
||||
listen [::]:8080;
|
||||
; server_name 10.43.88.1;
|
||||
; server_tokens off;
|
||||
; location /.well-known/acme-challenge/{
|
||||
; root /var/www/certbot;
|
||||
; }
|
||||
location / {
|
||||
root /usr/share/nginx/html/;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
; return 301 https://$host$request_uri;
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
; error_page 500 502 503 504 /50x.html;
|
||||
; location = /50x.html {
|
||||
; root /usr/share/nginx/html;
|
||||
; }
|
||||
}
|
||||
|
||||
; server {
|
||||
; listen 443 default_server ssl;
|
||||
; listen [::]:443 ssl;
|
||||
|
||||
; server_name 10.43.88.1;
|
||||
|
||||
; ssl_certificate /etc/nginx/ssl/live/10.43.88.1/fullchain.pem;
|
||||
; ssl_certificate_key /etc/nginx/ssl/live/10.43.88.1/privkey.pem;
|
||||
; include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
; location / {
|
||||
; root /usr/share/nginx/html/;
|
||||
; index index.html index.htm;
|
||||
; try_files $uri $uri/ /index.html;
|
||||
; }
|
||||
; }
|
||||
@@ -1,6 +1,10 @@
|
||||
|
||||
events {
|
||||
worker_connections 4096; ## Default: 1024
|
||||
}
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
listen 8080;
|
||||
listen [::]:8080;
|
||||
location / {
|
||||
root /usr/share/nginx/html/;
|
||||
index index.html index.htm;
|
||||
@@ -11,3 +15,4 @@ server {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-1
@@ -19,7 +19,19 @@
|
||||
"react-scripts": "5.0.0",
|
||||
"web-vitals": "^2.1.3",
|
||||
"@material-ui/core": "^4.12.3",
|
||||
"@material-ui/icons": "^4.11.2"
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"workbox-background-sync": "^5.1.4",
|
||||
"workbox-broadcast-update": "^5.1.4",
|
||||
"workbox-cacheable-response": "^5.1.4",
|
||||
"workbox-core": "^5.1.4",
|
||||
"workbox-expiration": "^5.1.4",
|
||||
"workbox-google-analytics": "^5.1.4",
|
||||
"workbox-navigation-preload": "^5.1.4",
|
||||
"workbox-precaching": "^5.1.4",
|
||||
"workbox-range-requests": "^5.1.4",
|
||||
"workbox-routing": "^5.1.4",
|
||||
"workbox-strategies": "^5.1.4",
|
||||
"workbox-streams": "^5.1.4"
|
||||
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.App {
|
||||
/* height: 100%; */
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
+8
-14
@@ -16,7 +16,7 @@ import NotFoundPage from "./Pages/NotFoundPage";
|
||||
import DashboardPage from "./Pages/DashboardPage/DashboardPage";
|
||||
import WelcomePage from "./Pages/WelcomePage";
|
||||
import InputPage from "./Pages/InputPage";
|
||||
import { createMuiTheme, ThemeProvider } from '@material-ui/core'
|
||||
import { createTheme, ThemeProvider } from '@material-ui/core/styles'
|
||||
import { Formik, Field, Form } from 'formik';
|
||||
import { TextField, Button, Grid, FormRow, Checkbox, Radio, FormControlLabel, FormControl, FormLabel, RadioGroup, IconButton, InputAdornment } from "@material-ui/core";
|
||||
import { AddCircleOutline, RemoveCircleOutline } from "@material-ui/icons";
|
||||
@@ -25,8 +25,7 @@ import { ProcessedDataBucketProvider } from "./ProcessedDataBucketContext";
|
||||
|
||||
|
||||
function App() {
|
||||
const darkTheme = createMuiTheme({
|
||||
|
||||
const darkTheme = createTheme({
|
||||
// Theme settings
|
||||
palette: {
|
||||
type: "dark",
|
||||
@@ -35,18 +34,13 @@ function App() {
|
||||
fontSize: 18
|
||||
}
|
||||
});
|
||||
const styles = {
|
||||
bigbution: {
|
||||
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="App">
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<DbProvider>
|
||||
<ProcessedDataBucketProvider>
|
||||
<DbProvider>
|
||||
<Router>
|
||||
<Navigation />
|
||||
<div className="App">
|
||||
<Routes>
|
||||
<Route path="/" element={<WelcomePage />} />
|
||||
<Route path="/Dashboard" element={<DashboardPage />} />
|
||||
@@ -54,11 +48,11 @@ function App() {
|
||||
<Route path="/404" element={<NotFoundPage />} />
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</ProcessedDataBucketProvider>
|
||||
</DbProvider>
|
||||
</ThemeProvider>
|
||||
</div>
|
||||
</Router>
|
||||
</DbProvider>
|
||||
</ProcessedDataBucketProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export function useRemoteDb() {
|
||||
}
|
||||
|
||||
export function DbProvider({ children }) {
|
||||
const pdb = React.useContext(ProcessedDataBucketContext);
|
||||
const [localdb, setLocaldb] = useState(new PouchDB("testdata"));
|
||||
//used in development server
|
||||
const [remotedb, setRemotedb] = useState(
|
||||
@@ -25,25 +26,16 @@ export function DbProvider({ children }) {
|
||||
},
|
||||
})
|
||||
);
|
||||
console.log(remotedb);
|
||||
// const [remotedb, setRemotedb] = useState(new PouchDB(window.location.protocol + "//" + window.location.hostname + ":5984/kcmt2021", {skip_setup: true}))
|
||||
|
||||
//Login to the Remote Database
|
||||
// remotedb.logIn('2021', 'Ridgebotics').then(function () {
|
||||
// console.log("CouchDb Login Successful!");
|
||||
// }).catch(function (err) {
|
||||
// console.log("Unable to login to CouchDb!");
|
||||
// console.log(err);
|
||||
// });
|
||||
|
||||
pdb.updateData(localdb);
|
||||
localdb
|
||||
.sync(remotedb, {
|
||||
live: true,
|
||||
retry: true,
|
||||
})
|
||||
.on("change", function (change) {
|
||||
const pdb = React.useContext(ProcessedDataBucketContext);
|
||||
console.log(pdb);
|
||||
console.log('DB CHANGED');
|
||||
pdb.updateData(localdb);
|
||||
})
|
||||
.on("paused", function (info) { })
|
||||
.on("active", function (info) { })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.maxwidth {
|
||||
display: block;
|
||||
/* margin-left: auto;
|
||||
margin-right: auto; */
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Button,
|
||||
Grid,
|
||||
FormRow,
|
||||
Divider,
|
||||
Checkbox,
|
||||
Radio,
|
||||
FormControlLabel,
|
||||
@@ -26,9 +27,8 @@ import { AddCircleOutline, RemoveCircleOutline } from "@material-ui/icons";
|
||||
|
||||
const InputPage = () => {
|
||||
const localdb = useLocalDb();
|
||||
console.log(localdb);
|
||||
return (
|
||||
<div class="maxwidth">
|
||||
<div>
|
||||
<br />
|
||||
<Formik
|
||||
initialValues={{
|
||||
@@ -39,17 +39,19 @@ const InputPage = () => {
|
||||
team_abilities_cant: "",
|
||||
fouls: "0",
|
||||
fouls_tech: "0",
|
||||
flips: "0",
|
||||
flipped: false,
|
||||
red_cards: "0",
|
||||
yellow_cards: "0",
|
||||
disabled: false,
|
||||
taxi_auto: false,
|
||||
upper_hub_auto: "0",
|
||||
lower_hub_auto: "0",
|
||||
upper_hub: "0",
|
||||
lower_hub: "0",
|
||||
upper_hub_teleop: "0",
|
||||
lower_hub_teleop: "0",
|
||||
climb_level: "",
|
||||
alliance: "",
|
||||
defence: ""
|
||||
defence: "0",
|
||||
disabled: false
|
||||
}}
|
||||
onSubmit={(values, { setSubmitting, resetForm }) => {
|
||||
setTimeout(() => {
|
||||
@@ -82,31 +84,13 @@ const InputPage = () => {
|
||||
handleSubmit,
|
||||
isSubmitting,
|
||||
}) => (
|
||||
|
||||
<Form >
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="center"
|
||||
alignItems="flex-start"
|
||||
spacing={3}>
|
||||
<Grid container direction="row" justifyContent="center" alignItems="flex-end" spacing={3}>
|
||||
<Grid item>
|
||||
{" "}
|
||||
<Field
|
||||
type="input"
|
||||
as={TextField}
|
||||
name="team_number"
|
||||
label="Team #"
|
||||
/>{" "}
|
||||
<Field type="input" as={TextField} name="team_number" label="Team #" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
{" "}
|
||||
<Field
|
||||
type="input"
|
||||
as={TextField}
|
||||
name="match_number"
|
||||
label="Match Number"
|
||||
/>{" "}
|
||||
<Field type="input" as={TextField} name="match_number" label="Match Number" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<FormControl component="fieldset">
|
||||
@@ -114,25 +98,13 @@ const InputPage = () => {
|
||||
<RadioGroup aria-label="Alliance" name="alliance" row>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="alliance"
|
||||
value="red"
|
||||
style={{ fontSize: 50 }}
|
||||
/>
|
||||
<Field as={Radio} type="radio" name="alliance" value="red" style={{ fontSize: 50 }} />
|
||||
}
|
||||
label="Red"
|
||||
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="alliance"
|
||||
value="blue"
|
||||
/>
|
||||
<Field as={Radio} type="radio" name="alliance" value="blue" />
|
||||
}
|
||||
label="Blue"
|
||||
/>
|
||||
@@ -140,118 +112,62 @@ const InputPage = () => {
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<div />
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="center"
|
||||
alignItems="flex-start"
|
||||
spacing={3}>
|
||||
<Grid container direction="row" justifyContent="center" alignItems="flex-end" spacing={3}>
|
||||
<Grid item>
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justify="flex-start"
|
||||
alignItems="center"
|
||||
spacing={3}>
|
||||
<Grid container direction="column" justifyContent="flex-end" alignItems="center" spacing={3}>
|
||||
<Grid item>
|
||||
<InputNumberField
|
||||
name="upper_hub_auto"
|
||||
label="Upper Hub Auto"
|
||||
/>
|
||||
<InputNumberField name="upper_hub_auto" label="Upper Hub Auto" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<InputNumberField
|
||||
name="lower_hub_auto"
|
||||
label="Lower Hub Auto"
|
||||
/>
|
||||
<InputNumberField name="lower_hub_auto" label="Lower Hub Auto" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
{" "}
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justify="flex-start"
|
||||
alignItems="center"
|
||||
spacing={3}>
|
||||
<Grid container direction="column" justifyContent="flex-end" alignItems="center" spacing={3}>
|
||||
<Grid item>
|
||||
{" "}
|
||||
<InputNumberField name="upper_hub" label="Upper Hub" />{" "}
|
||||
<InputNumberField name="upper_hub_teleop" label="Upper Hub Teleop" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
{" "}
|
||||
<InputNumberField name="lower_hub" label="Lower Hub" />{" "}
|
||||
</Grid>
|
||||
</Grid>{" "}
|
||||
<InputNumberField name="lower_hub_teleop" label="Lower Hub Teleop" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<div />
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="center"
|
||||
alignItems="flex-start"
|
||||
spacing={3}>
|
||||
<Grid container direction="row" justifyContent="center" alignItems="flex-end" spacing={3}>
|
||||
<Grid item>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Climbing</FormLabel>
|
||||
<RadioGroup aria-label="Climbing" name="climb_level" row>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="climb_level"
|
||||
value="0"
|
||||
/>
|
||||
<Field as={Radio} type="radio" name="climb_level" value="0" />
|
||||
}
|
||||
label="None"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="climb_level"
|
||||
value="1"
|
||||
/>
|
||||
<Field as={Radio} type="radio" name="climb_level" value="1" />
|
||||
}
|
||||
label="Low"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="climb_level"
|
||||
value="2"
|
||||
/>
|
||||
<Field as={Radio} type="radio" name="climb_level" value="2" />
|
||||
}
|
||||
label="Mid"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="climb_level"
|
||||
value="3"
|
||||
/>
|
||||
<Field as={Radio} type="radio" name="climb_level" value="3" />
|
||||
}
|
||||
label="High"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="climb_level"
|
||||
value="4"
|
||||
/>
|
||||
<Field as={Radio} type="radio" name="climb_level" value="4" />
|
||||
}
|
||||
label="Traversal"
|
||||
/>
|
||||
@@ -259,168 +175,77 @@ const InputPage = () => {
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<div />
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="center"
|
||||
alignItems="flex-start"
|
||||
spacing={3}>
|
||||
<Grid container direction="row" justifyContent="center" alignItems="flex-end" spacing={3}>
|
||||
<Grid item>
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justify="flex-start"
|
||||
alignItems="center"
|
||||
spacing={3}>
|
||||
<Grid container direction="column" justifyContent="flex-end" alignItems="center" spacing={3}>
|
||||
<Grid item>
|
||||
<InputNumberField name="fouls" label="Fouls" />{" "}
|
||||
</Grid>{" "}
|
||||
<Grid item>
|
||||
<InputNumberField name="fouls_tech" label="Tech Fouls" />{" "}
|
||||
</Grid>{" "}
|
||||
</Grid>
|
||||
<InputNumberField name="fouls" label="Fouls" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justify="flex-start"
|
||||
alignItems="center"
|
||||
spacing={3}>
|
||||
<Grid item>
|
||||
<InputNumberField name="red_cards" label="Red Cards" />{" "}
|
||||
</Grid>{" "}
|
||||
<Grid item>
|
||||
<InputNumberField name="yellow_cards" label="Yellow Cards" />{" "}
|
||||
</Grid>
|
||||
<Grid item>
|
||||
{/* <InputNumberField name="disables" label="Disables" />{" "} */}
|
||||
</Grid>{" "}
|
||||
<InputNumberField name="fouls_tech" label="Tech Fouls" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid container direction="column" justifyContent="flex-end" alignItems="center" spacing={3}>
|
||||
<Grid item>
|
||||
<InputNumberField name="red_cards" label="Red Cards" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<InputNumberField name="yellow_cards" label="Yellow Cards" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<div />
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="center"
|
||||
alignItems="flex-start"
|
||||
spacing={3}>
|
||||
<Grid container direction="row" justifyContent="center" alignItems="flex-end" spacing={3}>
|
||||
<Grid item>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Defense</FormLabel>
|
||||
<RadioGroup aria-label="Defense" name="defence" row>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="defence"
|
||||
value="0"
|
||||
/>
|
||||
}
|
||||
label="N/A"
|
||||
control={<Field as={Radio} type="radio" name="defence" value="0" />}
|
||||
label="None"
|
||||
/>
|
||||
{/* <Divider orientation="vertical" flexItem middle /> */}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="defence"
|
||||
value="1"
|
||||
/>
|
||||
}
|
||||
control={<Field as={Radio} type="radio" name="defence" value="1" />}
|
||||
label="Poor"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="defence"
|
||||
value="2"
|
||||
/>
|
||||
}
|
||||
control={<Field as={Radio} type="radio" name="defence" value="2" />}
|
||||
label="Good"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Field
|
||||
as={Radio}
|
||||
type="radio"
|
||||
name="defence"
|
||||
value="3"
|
||||
/>
|
||||
}
|
||||
/> <FormControlLabel
|
||||
control={<Field as={Radio} type="radio" name="defence" value="3" />}
|
||||
label="Exceptional"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<FormControlLabel
|
||||
control={<Field as={Checkbox} type="checkbox" name="disabled" />}
|
||||
label="Disabled"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<div />
|
||||
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="center"
|
||||
alignItems="flex-start"
|
||||
spacing={3}>
|
||||
<Grid container direction="row" justifyContent="center" alignItems="flex-end" spacing={3}>
|
||||
<Grid item>
|
||||
{" "}
|
||||
<Field
|
||||
type="input"
|
||||
as={TextField}
|
||||
multiline
|
||||
rows={3}
|
||||
rowsMax={7}
|
||||
labelWidth={60}
|
||||
name="team_abilities_well"
|
||||
label="What they did Well"
|
||||
/>{" "}
|
||||
<Field type="input" as={TextField} multiline rows={3} name="team_abilities_well" label="What they did Well" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
{" "}
|
||||
<Field
|
||||
type="input"
|
||||
as={TextField}
|
||||
multiline
|
||||
rows={3}
|
||||
rowsMax={7}
|
||||
labelWidth={60}
|
||||
name="team_abilities_struggle"
|
||||
label="What they struggled with"
|
||||
/>{" "}
|
||||
<Field type="input" as={TextField} multiline rows={3} name="team_abilities_struggle" label="What they struggled with" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
{" "}
|
||||
<Field
|
||||
type="input"
|
||||
as={TextField}
|
||||
multiline
|
||||
rows={3}
|
||||
rowsMax={7}
|
||||
labelWidth={60}
|
||||
name="team_abilities_cant"
|
||||
label="What they can't do"
|
||||
/>{" "}
|
||||
<Field type="input" as={TextField} multiline rows={3} name="team_abilities_cant" label="What they can't do" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<div />
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="center"
|
||||
alignItems="flex-start"
|
||||
spacing={3}>
|
||||
<Grid container direction="row" justifyContent="center" alignItems="flex-end" spacing={3}>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{" "}
|
||||
Submit{" "}
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
</Form>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.welcome {
|
||||
display: block;
|
||||
/* margin-left: auto;
|
||||
margin-right: auto; */
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
|
||||
@@ -3,7 +3,7 @@ import "./WelcomePage.css";
|
||||
import "../App.css";
|
||||
const WelcomePage = () => {
|
||||
return (
|
||||
<div class = "welcome">
|
||||
<div className="welcome">
|
||||
<h1>Welcome to Ridgebotics Scouting Web Application 2022</h1>
|
||||
<img src="/WelcomePageImage.webp" />
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
export class ProcessedDataBucket {
|
||||
constructor() {
|
||||
this.teamData = null;
|
||||
this.matchData = null;
|
||||
this.teamData = {};
|
||||
this.matchData = {};
|
||||
this.updateData = (db) => {
|
||||
//reset data
|
||||
this.teamData = {};
|
||||
this.matchData = {};
|
||||
console.log(db);
|
||||
|
||||
db.allDocs({
|
||||
include_docs: true,
|
||||
})
|
||||
@@ -11,55 +16,94 @@ export class ProcessedDataBucket {
|
||||
result.rows.forEach((dbentry) => {
|
||||
let doc = dbentry.doc;
|
||||
console.log(doc);
|
||||
//if there's no processed data on a team yet, create a default data entry
|
||||
if (typeof this.teamData[doc.team_name] === "undefined") {
|
||||
this.teamData[doc.team_name] = {
|
||||
team_name: doc.team_name,
|
||||
alliance: doc.alliance,
|
||||
games_played: 0,
|
||||
climbs_none: 0,
|
||||
climbs_low: 0,
|
||||
climbs_mid: 0,
|
||||
climbs_high: 0,
|
||||
climbs_transverse: 0,
|
||||
points: 0,
|
||||
point_average: 0,
|
||||
num_disables: 0,
|
||||
disables_average: 0,
|
||||
num_flips: 0,
|
||||
flips_average: 0,
|
||||
fouls: 0,
|
||||
fouls_average: 0,
|
||||
fouls_tech: 0,
|
||||
fouls_tech_average: 0,
|
||||
};
|
||||
}
|
||||
let thisTeamData = this.teamData[doc.team_name];
|
||||
console.log(thisTeamData);
|
||||
let new_team_data = {
|
||||
...thisTeamData,
|
||||
games_played: thisTeamData.games_played + 1,
|
||||
num_climbs: thisTeamData.num_climbs + (doc.climb == true ? 1 : 0),
|
||||
num_disables: thisTeamData.num_disables + (doc.disabled == true ? 1 : 0),
|
||||
num_flips: thisTeamData.num_flips + (doc.flipped_over == true ? 1 : 0),
|
||||
fouls: thisTeamData.fouls + parseInt(doc.fouls),
|
||||
fouls_tech: thisTeamData.fouls_tech + parseInt(doc.fouls_tech),
|
||||
inner_port: thisTeamData.inner_port + parseInt(doc.inner_port),
|
||||
outer_port: thisTeamData.outer_port + parseInt(doc.outer_port),
|
||||
lower_port: thisTeamData.lower_port + parseInt(doc.lower_port),
|
||||
};
|
||||
console.log(new_team_data);
|
||||
|
||||
// //if there's no processed data on a team yet, create a default data entry
|
||||
// if (typeof this.teamData[doc.team_number] === "undefined") {
|
||||
// this.teamData[doc.team_number] = {
|
||||
// team_number: doc.team_number,
|
||||
// games_played: 0,
|
||||
// data_sets: {
|
||||
// upper_hub_auto: [],
|
||||
// lower_hub_auto: [],
|
||||
// upper_hub: [],
|
||||
// lower_hub: [],
|
||||
// match_points: []
|
||||
// },
|
||||
// climbs_none: 0,
|
||||
// climbs_low: 0,
|
||||
// climbs_mid: 0,
|
||||
// climbs_high: 0,
|
||||
// climbs_transverse: 0,
|
||||
// points_average: 0,
|
||||
// num_disables: 0,
|
||||
// num_flips: 0,
|
||||
// fouls: 0,
|
||||
// fouls_tech: 0,
|
||||
// red_cards: 0,
|
||||
// yellow_cards: 0
|
||||
// };
|
||||
// }
|
||||
|
||||
// //add this game's data to the respective team data:
|
||||
// let thisTeamData = this.teamData[doc.team_number];
|
||||
// thisTeamData.games_played++;
|
||||
|
||||
// let match_points
|
||||
// = parseInt(doc.taxi_auto)
|
||||
// + parseInt(doc.upper_hub_auto) * 4
|
||||
// + parseInt(doc.lower_hub_auto) * 2
|
||||
// + parseInt(doc.upper_hub_teleop) * 2
|
||||
// + parseInt(doc.lower_hub_teleop) * 1
|
||||
// + (parseInt(doc.climb_level) == 0 ? 4 : 0)
|
||||
// + (parseInt(doc.climb_level) == 1 ? 6 : 0)
|
||||
// + (parseInt(doc.climb_level) == 2 ? 10 : 0)
|
||||
// + (parseInt(doc.climb_level) == 3 ? 15 : 0)
|
||||
// ;
|
||||
|
||||
// //data sets
|
||||
// thisTeamData.data_sets.upper_hub_auto.push(parseInt(doc.upper_hub_auto));
|
||||
// thisTeamData.data_sets.lower_hub_auto.push(parseInt(doc.lower_hub_auto));
|
||||
// thisTeamData.data_sets.upper_hub.push(parseInt(doc.upper_hub));
|
||||
// thisTeamData.data_sets.lower_hub.push(parseInt(doc.lower_hub));
|
||||
// thisTeamData.data_sets.match_points.push(match_points);
|
||||
|
||||
// //climb data
|
||||
// switch (parseInt(doc.climb_level)) {
|
||||
// case 0:
|
||||
// thisTeamData.climbs_none++;
|
||||
// break;
|
||||
// case 1:
|
||||
// thisTeamData.climbs_low++;
|
||||
// break;
|
||||
// case 2:
|
||||
// thisTeamData.climbs_mid++;
|
||||
// break;
|
||||
// case 3:
|
||||
// thisTeamData.climbs_high++;
|
||||
// break;
|
||||
// case 4:
|
||||
// thisTeamData.climbs_transverse++;
|
||||
// break;
|
||||
// default:
|
||||
// console.error('Invalid Climb Level (how did this even happen lol?): ' + doc.climb_level);
|
||||
// break;
|
||||
// }
|
||||
|
||||
// //misc data
|
||||
// thisTeamData.num_disables += doc.disabled ? 1 : 0;
|
||||
// thisTeamData.num_flips += doc.flipped ? 1 : 0;
|
||||
// thisTeamData.fouls += parseInt(doc.fouls);
|
||||
// thisTeamData.fouls_tech += parseInt(doc.fouls_tech);
|
||||
// thisTeamData.red_cards += parseInt(doc.red_cards);
|
||||
// thisTeamData.yellow_cards += parseInt(doc.yellow_cards);
|
||||
|
||||
// console.log(thisTeamData);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Error Fetching Docs from Database!");
|
||||
console.log(err);
|
||||
console.log("Error while processing data!");
|
||||
console.error(err);
|
||||
});
|
||||
let datasets = [
|
||||
{
|
||||
data: [],
|
||||
},
|
||||
];
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useLocalDb } from "./DbContext";
|
||||
import { ProcessedDataBucket } from "./ProcessedDataBucket.jsx"
|
||||
|
||||
export const ProcessedDataBucketContext = React.createContext();
|
||||
|
||||
@@ -25,16 +25,15 @@ const InputNumberField = (props) => {
|
||||
name={props.name}
|
||||
label={props.label}
|
||||
InputProps={{
|
||||
style: { fontSize: 40, "max-width": 300 },
|
||||
style: { fontSize: 40, "maxWidth": 300 },
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setFieldValue(props.name, Math.max(parseInt(values[props.name]) - 1, 0));
|
||||
}}
|
||||
iconStyle={{ width: '48px', height: '48px' }}
|
||||
style={{ width: '96px', height: '96px', padding: '24px' }}
|
||||
touch={true}
|
||||
touch="true"
|
||||
>
|
||||
<RemoveCircleOutline style={{ fontSize: 50 }} />
|
||||
</IconButton>
|
||||
@@ -46,11 +45,8 @@ const InputNumberField = (props) => {
|
||||
onClick={() => {
|
||||
setFieldValue(props.name, Math.max(parseInt(values[props.name]) + 1, 0));
|
||||
}}
|
||||
iconStyle={{ width: '100px', height: '100px' }}
|
||||
style={{ width: '96px', height: '96px', padding: '0' }}
|
||||
touch={true}
|
||||
size="large"
|
||||
|
||||
touch="true"
|
||||
>
|
||||
<AddCircleOutline style={{ fontSize: 50 }} />
|
||||
</IconButton>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.Navigation {
|
||||
position: -webkit-sticky; /*for safari*/
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, {Component} from 'react'
|
||||
import Toolbar from './Toolbar/Toolbar'
|
||||
import SideDrawer from './Drawer/SideDrawer'
|
||||
import './Navigation.css'
|
||||
|
||||
class Navigation extends Component {
|
||||
state = {
|
||||
@@ -15,7 +16,7 @@ class Navigation extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="Navigation">
|
||||
<Toolbar drawerClickHandler={this.drawerToggleClickHandler} />
|
||||
<SideDrawer show={this.state.sideDrawerOpen} drawerClickHandler={this.drawerToggleClickHandler}/>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
.toolbar {
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: #00a65a;
|
||||
height: 56px
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.toolbar_navigation {
|
||||
@@ -21,7 +18,7 @@
|
||||
.toolbar_logo a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 2.0rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.toolbar_spacer {
|
||||
|
||||
@@ -10,7 +10,7 @@ const Toolbar = props => {
|
||||
<header className="toolbar">
|
||||
<nav className="toolbar_navigation">
|
||||
<DrawerButton click={props.drawerClickHandler} />
|
||||
<div className = "toolbar_logo"><Link to="/">Ridgebotics Scouting</Link></div>
|
||||
<div className = "toolbar_logo"><Link to="/">Ridgebotics Scouting ;)</Link></div>
|
||||
<div className = "toolbar_spacer" />
|
||||
<div className = "toolbar_items">
|
||||
<PagesList />
|
||||
|
||||
@@ -2,11 +2,12 @@ body {
|
||||
background-color: rgb(20, 26, 24);
|
||||
color: white;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
@@ -2,7 +2,8 @@ import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
@@ -11,6 +12,11 @@ ReactDOM.render(
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://cra.link/PWA
|
||||
serviceWorkerRegistration.register();
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
|
||||
// This service worker can be customized!
|
||||
// See https://developers.google.com/web/tools/workbox/modules
|
||||
// for the list of available Workbox modules, or add any other
|
||||
// code you'd like.
|
||||
// You can also remove this file if you'd prefer not to use a
|
||||
// service worker, and the Workbox build step will be skipped.
|
||||
|
||||
import { clientsClaim } from 'workbox-core';
|
||||
import { ExpirationPlugin } from 'workbox-expiration';
|
||||
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
|
||||
import { registerRoute } from 'workbox-routing';
|
||||
import { StaleWhileRevalidate } from 'workbox-strategies';
|
||||
|
||||
clientsClaim();
|
||||
|
||||
// Precache all of the assets generated by your build process.
|
||||
// Their URLs are injected into the manifest variable below.
|
||||
// This variable must be present somewhere in your service worker file,
|
||||
// even if you decide not to use precaching. See https://cra.link/PWA
|
||||
precacheAndRoute(self.__WB_MANIFEST);
|
||||
|
||||
// Set up App Shell-style routing, so that all navigation requests
|
||||
// are fulfilled with your index.html shell. Learn more at
|
||||
// https://developers.google.com/web/fundamentals/architecture/app-shell
|
||||
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
|
||||
registerRoute(
|
||||
// Return false to exempt requests from being fulfilled by index.html.
|
||||
({ request, url }) => {
|
||||
// If this isn't a navigation, skip.
|
||||
if (request.mode !== 'navigate') {
|
||||
return false;
|
||||
} // If this is a URL that starts with /_, skip.
|
||||
|
||||
if (url.pathname.startsWith('/_')) {
|
||||
return false;
|
||||
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
|
||||
|
||||
if (url.pathname.match(fileExtensionRegexp)) {
|
||||
return false;
|
||||
} // Return true to signal that we want to use the handler.
|
||||
|
||||
return true;
|
||||
},
|
||||
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
|
||||
);
|
||||
|
||||
// An example runtime caching route for requests that aren't handled by the
|
||||
// precache, in this case same-origin .png requests like those from in public/
|
||||
registerRoute(
|
||||
// Add in any other file extensions or routing criteria as needed.
|
||||
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
|
||||
new StaleWhileRevalidate({
|
||||
cacheName: 'images',
|
||||
plugins: [
|
||||
// Ensure that once this runtime cache reaches a maximum size the
|
||||
// least-recently used images are removed.
|
||||
new ExpirationPlugin({ maxEntries: 50 }),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
// This allows the web app to trigger skipWaiting via
|
||||
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
|
||||
// Any other custom service worker logic can go here.
|
||||
@@ -0,0 +1,137 @@
|
||||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://cra.link/PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
|
||||
);
|
||||
|
||||
export function register(config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://cra.link/PWA'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl, config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then((registration) => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://cra.link/PWA.'
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl, {
|
||||
headers: { 'Service-Worker': 'script' },
|
||||
})
|
||||
.then((response) => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then((registration) => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log('No internet connection found. App is running in offline mode.');
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready
|
||||
.then((registration) => {
|
||||
registration.unregister();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user