166
.dev/bookmarks_importer.py
Executable file
|
@ -0,0 +1,166 @@
|
|||
import sqlite3
|
||||
from bs4 import BeautifulSoup
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from io import BytesIO
|
||||
import re
|
||||
import base64
|
||||
from datetime import datetime, timezone
|
||||
import os
|
||||
import argparse
|
||||
|
||||
|
||||
"""
|
||||
Imports html bookmarks file into Flame.
|
||||
Tested only on Firefox html exports so far.
|
||||
|
||||
Usage:
|
||||
python3 bookmarks_importer.py --bookmarks <path to bookmarks file> --data <path to flame data dir>
|
||||
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--bookmarks', type=str, required=True)
|
||||
parser.add_argument('--data', type=str, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
bookmarks_path = args.bookmarks
|
||||
data_path = args.data
|
||||
created = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + datetime.now().astimezone().strftime(" %z")
|
||||
updated = created
|
||||
if data_path[-1] != '/':
|
||||
data_path = data_path + '/'
|
||||
|
||||
|
||||
|
||||
|
||||
def Base64toPNG(codec, name):
|
||||
|
||||
"""
|
||||
Convert base64 encoded image to png file
|
||||
Reference: https://github.com/python-pillow/Pillow/issues/3400#issuecomment-428104239
|
||||
|
||||
Parameters:
|
||||
codec (str): icon in html bookmark format.e.g. 'data:image/png;base64,<image encoding>'
|
||||
name (str): name for export file
|
||||
|
||||
Returns:
|
||||
icon_name(str): name of png output E.g. 1636473849374--mybookmark.png
|
||||
None: if image not produced successfully
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
unix_t = str(int(datetime.now(tz=timezone.utc).timestamp() * 1000))
|
||||
icon_name = unix_t + '--' + re.sub(r'\W+', '', name).lower() + '.png'
|
||||
image_path = data_path + 'uploads/' + icon_name
|
||||
if os.path.exists(image_path):
|
||||
return image_path
|
||||
base64_data = re.sub('^data:image/.+;base64,', '', codec)
|
||||
byte_data = base64.b64decode(base64_data)
|
||||
image_data = BytesIO(byte_data)
|
||||
img = Image.open(image_data)
|
||||
img.save(image_path, "PNG")
|
||||
return icon_name
|
||||
except UnidentifiedImageError:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
def FlameBookmarkParser(bookmarks_path):
|
||||
|
||||
"""
|
||||
Parses HTML bookmarks file
|
||||
Reference: https://stackoverflow.com/questions/68621107/extracting-bookmarks-and-folder-hierarchy-from-google-chrome-with-beautifulsoup
|
||||
|
||||
Parameters:
|
||||
bookmarks_path (str): path to bookmarks.html
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
soup = BeautifulSoup()
|
||||
with open(bookmarks_path) as f:
|
||||
soup = BeautifulSoup(f.read(), 'lxml')
|
||||
|
||||
dt = soup.find_all('dt')
|
||||
folder_name =''
|
||||
for i in dt:
|
||||
n = i.find_next()
|
||||
if n.name == 'h3':
|
||||
folder_name = n.text
|
||||
continue
|
||||
else:
|
||||
url = n.get("href")
|
||||
website_name = n.text
|
||||
icon = n.get("icon")
|
||||
if icon != None:
|
||||
icon_name = Base64toPNG(icon, website_name)
|
||||
cat_id = AddFlameCategory(folder_name)
|
||||
AddFlameBookmark(website_name, url, cat_id, icon_name)
|
||||
|
||||
|
||||
|
||||
|
||||
def AddFlameCategory(cat_name):
|
||||
"""
|
||||
Parses HTML bookmarks file
|
||||
|
||||
Parameters:
|
||||
cat_name (str): category name
|
||||
|
||||
Returns:
|
||||
cat_id (int): primary key id of cat_name
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
con = sqlite3.connect(data_path + 'db.sqlite')
|
||||
cur = con.cursor()
|
||||
count_sql = ("SELECT count(*) FROM categories WHERE name = ?;")
|
||||
cur.execute(count_sql, [cat_name])
|
||||
count = int(cur.fetchall()[0][0])
|
||||
if count > 0:
|
||||
getid_sql = ("SELECT id FROM categories WHERE name = ?;")
|
||||
cur.execute(getid_sql, [cat_name])
|
||||
cat_id = int(cur.fetchall()[0][0])
|
||||
return cat_id
|
||||
|
||||
is_pinned = 1
|
||||
|
||||
insert_sql = "INSERT OR IGNORE INTO categories(name, isPinned, createdAt, updatedAt) VALUES (?, ?, ?, ?);"
|
||||
cur.execute(insert_sql, (cat_name, is_pinned, created, updated))
|
||||
con.commit()
|
||||
|
||||
getid_sql = ("SELECT id FROM categories WHERE name = ?;")
|
||||
cur.execute(getid_sql, [cat_name])
|
||||
cat_id = int(cur.fetchall()[0][0])
|
||||
return cat_id
|
||||
|
||||
|
||||
|
||||
|
||||
def AddFlameBookmark(website_name, url, cat_id, icon_name):
|
||||
con = sqlite3.connect(data_path + 'db.sqlite')
|
||||
cur = con.cursor()
|
||||
if icon_name == None:
|
||||
insert_sql = "INSERT OR IGNORE INTO bookmarks(name, url, categoryId, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?);"
|
||||
cur.execute(insert_sql, (website_name, url, cat_id, created, updated))
|
||||
con.commit()
|
||||
else:
|
||||
insert_sql = "INSERT OR IGNORE INTO bookmarks(name, url, categoryId, icon, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?);"
|
||||
cur.execute(insert_sql, (website_name, url, cat_id, icon_name, created, updated))
|
||||
con.commit()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
FlameBookmarkParser(bookmarks_path)
|
9
.dev/getMdi.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Script to get all icon names from materialdesignicons.com
|
||||
const getMdi = () => {
|
||||
const icons = document.querySelectorAll('#icons div span');
|
||||
const names = [...icons].map((icon) => icon.textContent.replace('mdi-', ''));
|
||||
const output = names.map((name) => ({ name }));
|
||||
output.pop();
|
||||
const json = JSON.stringify(output);
|
||||
console.log(json);
|
||||
};
|
2
.env
|
@ -1,3 +1,3 @@
|
|||
PORT=5005
|
||||
NODE_ENV=development
|
||||
VERSION=1.7.3
|
||||
VERSION=1.7.4
|
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
node_modules
|
||||
data
|
||||
public
|
||||
!client/public
|
||||
build.sh
|
|
@ -1,3 +1,10 @@
|
|||
### v1.7.4 (2021-11-08)
|
||||
- Added option to set custom greetings and date ([#103](https://github.com/pawelmalak/flame/issues/103))
|
||||
- Fallback to web search if local search has zero results ([#129](https://github.com/pawelmalak/flame/issues/129))
|
||||
- Added iOS "Add to homescreen" icon ([#131](https://github.com/pawelmalak/flame/issues/131))
|
||||
- Added experimental script to import bookmarks ([#141](https://github.com/pawelmalak/flame/issues/141))
|
||||
- Added 3 new themes
|
||||
|
||||
### v1.7.3 (2021-10-28)
|
||||
- Fixed bug with custom CSS not updating
|
||||
|
||||
|
|
10
Dockerfile
|
@ -1,6 +1,4 @@
|
|||
FROM node:14-alpine
|
||||
|
||||
RUN apk update && apk add --no-cache nano curl
|
||||
FROM node:14 as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
@ -18,6 +16,12 @@ RUN mkdir -p ./public ./data \
|
|||
&& mv ./client/build/* ./public \
|
||||
&& rm -rf ./client
|
||||
|
||||
FROM node:14-alpine
|
||||
|
||||
COPY --from=builder /app /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
EXPOSE 5005
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
FROM node:14-alpine
|
||||
|
||||
RUN apk update && apk add --no-cache nano curl
|
||||
FROM node:14 as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN apk --no-cache --virtual build-dependencies add python make g++ \
|
||||
&& npm install --production
|
||||
RUN npm install --production
|
||||
|
||||
COPY . .
|
||||
|
||||
|
@ -17,8 +14,13 @@ RUN mkdir -p ./public ./data \
|
|||
&& npm run build \
|
||||
&& cd .. \
|
||||
&& mv ./client/build/* ./public \
|
||||
&& rm -rf ./client \
|
||||
&& apk del build-dependencies
|
||||
&& rm -rf ./client
|
||||
|
||||
FROM node:14-alpine
|
||||
|
||||
COPY --from=builder /app /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
EXPOSE 5005
|
||||
|
||||
|
|
74
README.md
|
@ -1,15 +1,10 @@
|
|||
# Flame
|
||||
|
||||
[![JS Badge](https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)](https://shields.io/)
|
||||
[![TS Badge](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)](https://shields.io/)
|
||||
[![Node Badge](https://img.shields.io/badge/Node.js-43853D?style=for-the-badge&logo=node.js&logoColor=white)](https://shields.io/)
|
||||
[![React Badge](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB)](https://shields.io/)
|
||||
|
||||
![Homescreen screenshot](./.github/_home.png)
|
||||
|
||||
## Description
|
||||
|
||||
Flame is self-hosted startpage for your server. Its design is inspired (heavily) by [SUI](https://github.com/jeroenpardon/sui). Flame is very easy to setup and use. With built-in editors it allows you to setup your very own appliaction hub in no time - no file editing necessary.
|
||||
Flame is self-hosted startpage for your server. Its design is inspired (heavily) by [SUI](https://github.com/jeroenpardon/sui). Flame is very easy to setup and use. With built-in editors it allows you to setup your very own application hub in no time - no file editing necessary.
|
||||
|
||||
## Technology
|
||||
|
||||
|
@ -42,7 +37,15 @@ npm run dev
|
|||
|
||||
### With Docker (recommended)
|
||||
|
||||
[Docker Hub](https://hub.docker.com/r/pawelmalak/flame)
|
||||
[Docker Hub link](https://hub.docker.com/r/pawelmalak/flame)
|
||||
|
||||
```sh
|
||||
docker pull pawelmalak/flame:latest
|
||||
|
||||
# for ARM architecture (e.g. RaspberryPi)
|
||||
docker pull pawelmalak/flame:multiarch
|
||||
```
|
||||
|
||||
|
||||
#### Building images
|
||||
|
||||
|
@ -96,13 +99,14 @@ Follow instructions from wiki: [Installation without Docker](https://github.com/
|
|||
|
||||
- Applications
|
||||
- Create, update, delete and organize applications using GUI
|
||||
- Pin your favourite apps to homescreen
|
||||
- Pin your favourite apps to the homescreen
|
||||
|
||||
![Homescreen screenshot](./.github/_apps.png)
|
||||
|
||||
- Bookmarks
|
||||
- Create, update, delete and organize bookmarks and categories using GUI
|
||||
- Pin your favourite categories to homescreen
|
||||
- Pin your favourite categories to the homescreen
|
||||
- Import html bookmarks (experimental)
|
||||
|
||||
![Homescreen screenshot](./.github/_bookmarks.png)
|
||||
|
||||
|
@ -111,7 +115,7 @@ Follow instructions from wiki: [Installation without Docker](https://github.com/
|
|||
- Get current temperature, cloud coverage and weather status with animated icons
|
||||
|
||||
- Themes
|
||||
- Customize your page by choosing from 12 color themes
|
||||
- Customize your page by choosing from 15 color themes
|
||||
|
||||
![Homescreen screenshot](./.github/_themes.png)
|
||||
|
||||
|
@ -125,23 +129,7 @@ To use search bar you need to type your search query with selected prefix. For e
|
|||
|
||||
> You can change where to open search results (same/new tab) in the settings
|
||||
|
||||
#### Supported search engines
|
||||
|
||||
| Name | Prefix | Search URL |
|
||||
| ---------- | ------ | ----------------------------------- |
|
||||
| Disroot | /ds | http://search.disroot.org/search?q= |
|
||||
| DuckDuckGo | /d | https://duckduckgo.com/?q= |
|
||||
| Google | /g | https://www.google.com/search?q= |
|
||||
|
||||
#### Supported services
|
||||
|
||||
| Name | Prefix | Search URL |
|
||||
| ------------------ | ------ | --------------------------------------------- |
|
||||
| IMDb | /im | https://www.imdb.com/find?q= |
|
||||
| Reddit | /r | https://www.reddit.com/search?q= |
|
||||
| Spotify | /sp | https://open.spotify.com/search/ |
|
||||
| The Movie Database | /mv | https://www.themoviedb.org/search?query= |
|
||||
| Youtube | /yt | https://www.youtube.com/results?search_query= |
|
||||
For list of supported search engines, shortcuts and more about searching functionality visit [project wiki](https://github.com/pawelmalak/flame/wiki/Search-bar).
|
||||
|
||||
### Setting up weather module
|
||||
|
||||
|
@ -159,13 +147,13 @@ labels:
|
|||
- flame.type=application # "app" works too
|
||||
- flame.name=My container
|
||||
- flame.url=https://example.com
|
||||
- flame.icon=icon-name # Optional, default is "docker"
|
||||
- flame.icon=icon-name # optional, default is "docker"
|
||||
# - flame.icon=custom to make changes in app. ie: custom icon upload
|
||||
```
|
||||
|
||||
And you must have activated the Docker sync option in the settings panel.
|
||||
> "Use Docker API" option must be enabled for this to work. You can find it in Settings > Other > Docker section
|
||||
|
||||
You can set up different apps in the same label adding `;` between each one.
|
||||
You can also set up different apps in the same label adding `;` between each one.
|
||||
|
||||
```yml
|
||||
labels:
|
||||
|
@ -208,13 +196,27 @@ metadata:
|
|||
- flame.pawelmalak/type=application # "app" works too
|
||||
- flame.pawelmalak/name=My container
|
||||
- flame.pawelmalak/url=https://example.com
|
||||
- flame.pawelmalak/icon=icon-name # Optional, default is "kubernetes"
|
||||
- flame.pawelmalak/icon=icon-name # optional, default is "kubernetes"
|
||||
```
|
||||
|
||||
And you must have activated the Kubernetes sync option in the settings panel.
|
||||
> "Use Kubernetes Ingress API" option must be enabled for this to work. You can find it in Settings > Other > Kubernetes section
|
||||
|
||||
### Custom CSS
|
||||
### Import HTML Bookmarks (Experimental)
|
||||
|
||||
> This is an experimental feature. Its behaviour might change in the future.
|
||||
>
|
||||
> Follow instructions from wiki: [Custom CSS](https://github.com/pawelmalak/flame/wiki/Custom-CSS)
|
||||
- Requirements
|
||||
- python3
|
||||
- pip packages: Pillow, beautifulsoup4
|
||||
- Backup your `db.sqlite` before running script!
|
||||
- Known Issues:
|
||||
- generated icons are sometimes incorrect
|
||||
|
||||
```bash
|
||||
pip3 install Pillow, beautifulsoup4
|
||||
|
||||
cd flame/.dev
|
||||
python3 bookmarks_importer.py --bookmarks <path to bookmarks.html> --data <path to flame data folder>
|
||||
```
|
||||
|
||||
### Custom CSS and themes
|
||||
|
||||
See project wiki for [Custom CSS](https://github.com/pawelmalak/flame/wiki/Custom-CSS) and [Custom theme with CSS](https://github.com/pawelmalak/flame/wiki/Custom-theme-with-CSS).
|
||||
|
|
|
@ -1 +1 @@
|
|||
REACT_APP_VERSION=1.7.3
|
||||
REACT_APP_VERSION=1.7.4
|
434
client/package-lock.json
generated
|
@ -1806,9 +1806,9 @@
|
|||
}
|
||||
},
|
||||
"@mdi/js": {
|
||||
"version": "5.9.55",
|
||||
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-5.9.55.tgz",
|
||||
"integrity": "sha512-BbeHMgeK2/vjdJIRnx12wvQ6s8xAYfvMmEAVsUx9b+7GiQGQ9Za8jpwp17dMKr9CgKRvemlAM4S7S3QOtEbp4A=="
|
||||
"version": "6.4.95",
|
||||
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-6.4.95.tgz",
|
||||
"integrity": "sha512-b1/P//1D2KOzta8YRGyoSLGsAlWyUHfxzVBhV4e/ppnjM4DfBgay/vWz7Eg5Ee80JZ4zsQz8h54X+KOahtBk5Q=="
|
||||
},
|
||||
"@mdi/react": {
|
||||
"version": "1.5.0",
|
||||
|
@ -2047,20 +2047,45 @@
|
|||
}
|
||||
},
|
||||
"@testing-library/dom": {
|
||||
"version": "7.30.4",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.30.4.tgz",
|
||||
"integrity": "sha512-GObDVMaI4ARrZEXaRy4moolNAxWPKvEYNV/fa6Uc2eAzR/t4otS6A7EhrntPBIQLeehL9DbVhscvvv7gd6hWqA==",
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.0.tgz",
|
||||
"integrity": "sha512-8Ay4UDiMlB5YWy+ZvCeRyFFofs53ebxrWnOFvCoM1HpMAX4cHyuSrCuIM9l2lVuUWUt+Gr3loz/nCwdrnG6ShQ==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/aria-query": "^4.2.0",
|
||||
"aria-query": "^4.2.2",
|
||||
"aria-query": "^5.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
"dom-accessibility-api": "^0.5.4",
|
||||
"dom-accessibility-api": "^0.5.9",
|
||||
"lz-string": "^1.4.4",
|
||||
"pretty-format": "^26.6.2"
|
||||
"pretty-format": "^27.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jest/types": {
|
||||
"version": "27.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz",
|
||||
"integrity": "sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ==",
|
||||
"requires": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^16.0.0",
|
||||
"chalk": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "16.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
|
||||
"integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==",
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
|
@ -2069,10 +2094,15 @@
|
|||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"aria-query": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz",
|
||||
"integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg=="
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
||||
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
|
@ -2096,6 +2126,29 @@
|
|||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "27.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz",
|
||||
"integrity": "sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA==",
|
||||
"requires": {
|
||||
"@jest/types": "^27.2.5",
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
@ -2107,9 +2160,9 @@
|
|||
}
|
||||
},
|
||||
"@testing-library/jest-dom": {
|
||||
"version": "5.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.12.0.tgz",
|
||||
"integrity": "sha512-N9Y82b2Z3j6wzIoAqajlKVF1Zt7sOH0pPee0sUHXHc5cv2Fdn23r+vpWm0MBBoGJtPOly5+Bdx1lnc3CD+A+ow==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.15.0.tgz",
|
||||
"integrity": "sha512-lOMuQidnL1tWHLEWIhL6UvSZC1Qt3OkNe1khvi2h6xFiqpe5O8arYs46OU0qyUGq0cSTbroQyMktYNXu3a7sAA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"@types/testing-library__jest-dom": "^5.9.1",
|
||||
|
@ -2117,6 +2170,7 @@
|
|||
"chalk": "^3.0.0",
|
||||
"css": "^3.0.0",
|
||||
"css.escape": "^1.5.1",
|
||||
"dom-accessibility-api": "^0.5.6",
|
||||
"lodash": "^4.17.15",
|
||||
"redent": "^3.0.0"
|
||||
},
|
||||
|
@ -2191,18 +2245,18 @@
|
|||
}
|
||||
},
|
||||
"@testing-library/react": {
|
||||
"version": "11.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.6.tgz",
|
||||
"integrity": "sha512-TXMCg0jT8xmuU8BkKMtp8l7Z50Ykew5WNX8UoIKTaLFwKkP2+1YDhOLA2Ga3wY4x29jyntk7EWfum0kjlYiSjQ==",
|
||||
"version": "12.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.2.tgz",
|
||||
"integrity": "sha512-ihQiEOklNyHIpo2Y8FREkyD1QAea054U0MVbwH1m8N9TxeFz+KoJ9LkqoKqJlzx2JDm56DVwaJ1r36JYxZM05g==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@testing-library/dom": "^7.28.1"
|
||||
"@testing-library/dom": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"@testing-library/user-event": {
|
||||
"version": "12.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.8.3.tgz",
|
||||
"integrity": "sha512-IR0iWbFkgd56Bu5ZI/ej8yQwrkCv8Qydx6RzwbKz9faXazR/+5tvYKsZQgyXJiwgpcva127YO6JcWy7YlCfofQ==",
|
||||
"version": "13.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz",
|
||||
"integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
}
|
||||
|
@ -2213,9 +2267,9 @@
|
|||
"integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA=="
|
||||
},
|
||||
"@types/aria-query": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz",
|
||||
"integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg=="
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
|
||||
"integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig=="
|
||||
},
|
||||
"@types/babel__core": {
|
||||
"version": "7.1.14",
|
||||
|
@ -2286,9 +2340,9 @@
|
|||
}
|
||||
},
|
||||
"@types/history": {
|
||||
"version": "4.7.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz",
|
||||
"integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA=="
|
||||
"version": "4.7.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
|
||||
"integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ=="
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
|
@ -2305,9 +2359,9 @@
|
|||
"integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA=="
|
||||
},
|
||||
"@types/http-proxy": {
|
||||
"version": "1.17.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.6.tgz",
|
||||
"integrity": "sha512-+qsjqR75S/ib0ig0R9WN+CDoZeOBU6F2XLewgC4KVgdXiNHiKKHFEMRHOrs5PbYE97D5vataw5wPj4KLYfUkuQ==",
|
||||
"version": "1.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz",
|
||||
"integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
|
@ -2334,12 +2388,126 @@
|
|||
}
|
||||
},
|
||||
"@types/jest": {
|
||||
"version": "26.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz",
|
||||
"integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==",
|
||||
"version": "27.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz",
|
||||
"integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==",
|
||||
"requires": {
|
||||
"jest-diff": "^26.0.0",
|
||||
"pretty-format": "^26.0.0"
|
||||
"jest-diff": "^27.0.0",
|
||||
"pretty-format": "^27.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jest/types": {
|
||||
"version": "27.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz",
|
||||
"integrity": "sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ==",
|
||||
"requires": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^16.0.0",
|
||||
"chalk": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "16.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
|
||||
"integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==",
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"diff-sequences": {
|
||||
"version": "27.0.6",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz",
|
||||
"integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
},
|
||||
"jest-diff": {
|
||||
"version": "27.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.3.1.tgz",
|
||||
"integrity": "sha512-PCeuAH4AWUo2O5+ksW4pL9v5xJAcIKPUPfIhZBcG1RKv/0+dvaWTQK1Nrau8d67dp65fOqbeMdoil+6PedyEPQ==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^27.0.6",
|
||||
"jest-get-type": "^27.3.1",
|
||||
"pretty-format": "^27.3.1"
|
||||
}
|
||||
},
|
||||
"jest-get-type": {
|
||||
"version": "27.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.3.1.tgz",
|
||||
"integrity": "sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg=="
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "27.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz",
|
||||
"integrity": "sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA==",
|
||||
"requires": {
|
||||
"@jest/types": "^27.2.5",
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/json-schema": {
|
||||
|
@ -2358,9 +2526,9 @@
|
|||
"integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "12.20.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.12.tgz",
|
||||
"integrity": "sha512-KQZ1al2hKOONAs2MFv+yTQP1LkDWMrRJ9YCVRalXltOfXsBmH5IownLxQaiq0lnAHwAViLnh2aTYqrPcRGEbgg=="
|
||||
"version": "16.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz",
|
||||
"integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w=="
|
||||
},
|
||||
"@types/normalize-package-data": {
|
||||
"version": "2.4.0",
|
||||
|
@ -2378,9 +2546,9 @@
|
|||
"integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA=="
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
|
||||
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
|
||||
"version": "15.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
|
||||
"integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ=="
|
||||
},
|
||||
"@types/q": {
|
||||
"version": "1.5.4",
|
||||
|
@ -2388,35 +2556,43 @@
|
|||
"integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug=="
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "17.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.5.tgz",
|
||||
"integrity": "sha512-bj4biDB9ZJmGAYTWSKJly6bMr4BLUiBrx9ujiJEoP9XIDY9CTaPGxE5QWN/1WjpPLzYF7/jRNnV2nNxNe970sw==",
|
||||
"version": "17.0.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz",
|
||||
"integrity": "sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==",
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@types/react-autosuggest": {
|
||||
"version": "10.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-autosuggest/-/react-autosuggest-10.1.5.tgz",
|
||||
"integrity": "sha512-qfMzrp6Is0VYRF5a97Bv/+P2F9ZtFY4YE2825yyWV4VxCpvcfvQHEqGNkDFIPme7t3B2BpQ784QBllYAxemERQ==",
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-beautiful-dnd": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz",
|
||||
"integrity": "sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg==",
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz",
|
||||
"integrity": "sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==",
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"version": "17.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.3.tgz",
|
||||
"integrity": "sha512-4NnJbCeWE+8YBzupn/YrJxZ8VnjcJq5iR1laqQ1vkpQgBiA7bwk0Rp24fxsdNinzJY2U+HHS4dJJDPdoMjdJ7w==",
|
||||
"version": "17.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz",
|
||||
"integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==",
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-redux": {
|
||||
"version": "7.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz",
|
||||
"integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==",
|
||||
"version": "7.1.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.20.tgz",
|
||||
"integrity": "sha512-q42es4c8iIeTgcnB+yJgRTTzftv3eYYvCZOh1Ckn2eX/3o5TdsQYKUWpLoLuGlcY/p+VAhV9IOEZJcWk/vfkXw==",
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
|
@ -2425,9 +2601,9 @@
|
|||
}
|
||||
},
|
||||
"@types/react-router": {
|
||||
"version": "5.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.14.tgz",
|
||||
"integrity": "sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw==",
|
||||
"version": "5.1.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
|
||||
"integrity": "sha512-RNSXOyb3VyRs/EOGmjBhhGKTbnN6fHWvy5FNLzWfOWOGjgVUKqJZXfpKzLmgoU8h6Hj8mpALj/mbXQASOb92wQ==",
|
||||
"requires": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*"
|
||||
|
@ -2452,9 +2628,9 @@
|
|||
}
|
||||
},
|
||||
"@types/scheduler": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz",
|
||||
"integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA=="
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
|
||||
},
|
||||
"@types/source-list-map": {
|
||||
"version": "0.1.2",
|
||||
|
@ -2472,9 +2648,9 @@
|
|||
"integrity": "sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ=="
|
||||
},
|
||||
"@types/testing-library__jest-dom": {
|
||||
"version": "5.9.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz",
|
||||
"integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==",
|
||||
"version": "5.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.1.tgz",
|
||||
"integrity": "sha512-Gk9vaXfbzc5zCXI9eYE9BI5BNHEp4D3FWjgqBE/ePGYElLAP+KvxBcsdkwfIVvezs605oiyd/VrpiHe3Oeg+Aw==",
|
||||
"requires": {
|
||||
"@types/jest": "*"
|
||||
}
|
||||
|
@ -3159,11 +3335,18 @@
|
|||
"integrity": "sha512-1uIESzroqpaTzt9uX48HO+6gfnKu3RwvWdCcWSrX4csMInJfCo1yvKPNXCwXFRpJqRW25tiASb6No0YH57PXqg=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.10.0"
|
||||
"follow-redirects": "^1.14.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"follow-redirects": {
|
||||
"version": "1.14.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz",
|
||||
"integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"axobject-query": {
|
||||
|
@ -4906,9 +5089,9 @@
|
|||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
|
||||
"integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw=="
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz",
|
||||
"integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw=="
|
||||
},
|
||||
"cyclist": {
|
||||
"version": "1.0.1",
|
||||
|
@ -5227,9 +5410,9 @@
|
|||
}
|
||||
},
|
||||
"dom-accessibility-api": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz",
|
||||
"integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ=="
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz",
|
||||
"integrity": "sha512-Xu9mD0UjrJisTmv7lmVSDMagQcU9R5hwAbxsaAE/35XPnPLJobbuREfV/rraiSaEj/UOvgrzQs66zyTWTlyd+g=="
|
||||
},
|
||||
"dom-converter": {
|
||||
"version": "0.2.0",
|
||||
|
@ -5577,6 +5760,11 @@
|
|||
"es6-symbol": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
||||
},
|
||||
"es6-symbol": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
|
||||
|
@ -7481,9 +7669,9 @@
|
|||
}
|
||||
},
|
||||
"http-proxy-middleware": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.0.tgz",
|
||||
"integrity": "sha512-S+RN5njuyvYV760aiVKnyuTXqUMcSIvYOsHA891DOVQyrdZOwaXtBHpt9FUVPEDAsOvsPArZp6VXQLs44yvkow==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz",
|
||||
"integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==",
|
||||
"requires": {
|
||||
"@types/http-proxy": "^1.17.5",
|
||||
"http-proxy": "^1.18.1",
|
||||
|
@ -12114,9 +12302,9 @@
|
|||
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
|
||||
"integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
|
||||
"integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-bytes": {
|
||||
|
@ -12407,6 +12595,18 @@
|
|||
"whatwg-fetch": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"react-autosuggest": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-autosuggest/-/react-autosuggest-10.1.0.tgz",
|
||||
"integrity": "sha512-/azBHmc6z/31s/lBf6irxPf/7eejQdR0IqnZUzjdSibtlS8+Rw/R79pgDAo6Ft5QqCUTyEQ+f0FhL+1olDQ8OA==",
|
||||
"requires": {
|
||||
"es6-promise": "^4.2.8",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-themeable": "^1.1.0",
|
||||
"section-iterator": "^2.0.0",
|
||||
"shallow-equal": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"react-beautiful-dnd": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
|
||||
|
@ -12548,16 +12748,31 @@
|
|||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz",
|
||||
"integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==",
|
||||
"version": "7.2.6",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
|
||||
"integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.13.1"
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.0.tgz",
|
||||
"integrity": "sha512-Nht8L0O8YCktmsDV6FqFue7vQLRx3Hb0B37lS5y0jDRqRxlBG4wIJHnf9/bgSE2UyipKFA01YtS+npRdTWBUyw==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-refresh": {
|
||||
|
@ -12677,6 +12892,21 @@
|
|||
"workbox-webpack-plugin": "5.1.4"
|
||||
}
|
||||
},
|
||||
"react-themeable": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz",
|
||||
"integrity": "sha1-fURm3ZsrX6dQWHJ4JenxUro3mg4=",
|
||||
"requires": {
|
||||
"object-assign": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"object-assign": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
|
||||
"integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I="
|
||||
}
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||
|
@ -12793,9 +13023,9 @@
|
|||
}
|
||||
},
|
||||
"redux": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.0.tgz",
|
||||
"integrity": "sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz",
|
||||
"integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
|
@ -12806,9 +13036,9 @@
|
|||
"integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A=="
|
||||
},
|
||||
"redux-thunk": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
||||
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.0.tgz",
|
||||
"integrity": "sha512-/y6ZKQNU/0u8Bm7ROLq9Pt/7lU93cT0IucYMrubo89ENjxPa7i8pqLKu6V4X7/TvYovQ6x01unTeyeZ9lgXiTA=="
|
||||
},
|
||||
"regenerate": {
|
||||
"version": "1.4.2",
|
||||
|
@ -13511,6 +13741,11 @@
|
|||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"section-iterator": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz",
|
||||
"integrity": "sha1-v0RNev7rlK1Dw5rS+yYVFifMuio="
|
||||
},
|
||||
"select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
|
@ -13685,6 +13920,11 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"shallow-equal": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
|
||||
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
|
@ -14926,9 +15166,9 @@
|
|||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
|
||||
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg=="
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
|
||||
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA=="
|
||||
},
|
||||
"unbox-primitive": {
|
||||
"version": "1.0.1",
|
||||
|
@ -15525,9 +15765,9 @@
|
|||
}
|
||||
},
|
||||
"web-vitals": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-1.1.2.tgz",
|
||||
"integrity": "sha512-PFMKIY+bRSXlMxVAQ+m2aw9c/ioUYfDgrYot0YUa+/xa0sakubWhSDyxAKwzymvXVdF4CZI71g06W+mqhzu6ig=="
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.2.tgz",
|
||||
"integrity": "sha512-nZnEH8dj+vJFqCRYdvYv0a59iLXsb8jJkt+xvXfwgnkyPdsSLtKNlYmtTDiHmTNGXeSXtpjTTUcNvFtrAk6VMQ=="
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "6.1.0",
|
||||
|
|
|
@ -3,33 +3,35 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@mdi/js": "^5.9.55",
|
||||
"@mdi/js": "^6.4.95",
|
||||
"@mdi/react": "^1.5.0",
|
||||
"@testing-library/jest-dom": "^5.12.0",
|
||||
"@testing-library/react": "^11.2.6",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^12.20.12",
|
||||
"@types/react": "^17.0.5",
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"@testing-library/jest-dom": "^5.15.0",
|
||||
"@testing-library/react": "^12.1.2",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/react": "^17.0.34",
|
||||
"@types/react-autosuggest": "^10.1.5",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"axios": "^0.21.1",
|
||||
"axios": "^0.24.0",
|
||||
"external-svg-loader": "^1.3.4",
|
||||
"http-proxy-middleware": "^2.0.0",
|
||||
"http-proxy-middleware": "^2.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-autosuggest": "^10.1.0",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"redux": "^4.1.0",
|
||||
"redux": "^4.1.2",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"redux-thunk": "^2.4.0",
|
||||
"skycons-ts": "^0.2.0",
|
||||
"typescript": "^4.2.4",
|
||||
"web-vitals": "^1.1.2"
|
||||
"typescript": "^4.4.4",
|
||||
"web-vitals": "^2.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -56,6 +58,6 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.3.2"
|
||||
"prettier": "^2.4.1"
|
||||
}
|
||||
}
|
||||
|
|
BIN
client/public/icons/apple-touch-icon-114x114.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
client/public/icons/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
client/public/icons/apple-touch-icon-144x144.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
client/public/icons/apple-touch-icon-152x152.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
client/public/icons/apple-touch-icon-180x180.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
client/public/icons/apple-touch-icon-57x57.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
client/public/icons/apple-touch-icon-72x72.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
client/public/icons/apple-touch-icon-76x76.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
client/public/icons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -2,7 +2,51 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/icons/favicon.ico" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
href="%PUBLIC_URL%/icons/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="57x57"
|
||||
href="%PUBLIC_URL%/icons/apple-touch-icon-57x57.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="72x72"
|
||||
href="%PUBLIC_URL%/icons/apple-touch-icon-72x72.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="76x76"
|
||||
href="%PUBLIC_URL%/icons/apple-touch-icon-76x76.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="114x114"
|
||||
href="%PUBLIC_URL%/icons/apple-touch-icon-114x114.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="120x120"
|
||||
href="%PUBLIC_URL%/icons/apple-touch-icon-120x120.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="144x144"
|
||||
href="%PUBLIC_URL%/icons/apple-touch-icon-144x144.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="152x152"
|
||||
href="%PUBLIC_URL%/icons/apple-touch-icon-152x152.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="%PUBLIC_URL%/icons/apple-touch-icon-180x180.png"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta
|
||||
name="description"
|
||||
|
|
31
client/src/components/Home/Header/Header.module.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
.Header h1 {
|
||||
color: var(--color-primary);
|
||||
font-weight: 700;
|
||||
font-size: 4em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.Header p {
|
||||
color: var(--color-primary);
|
||||
font-weight: 300;
|
||||
text-transform: uppercase;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.HeaderMain {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.SettingsLink {
|
||||
visibility: visible;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.SettingsLink {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
49
client/src/components/Home/Header/Header.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Config, GlobalState } from '../../../interfaces';
|
||||
import WeatherWidget from '../../Widgets/WeatherWidget/WeatherWidget';
|
||||
import { getDateTime } from './functions/getDateTime';
|
||||
import { greeter } from './functions/greeter';
|
||||
import classes from './Header.module.css';
|
||||
|
||||
interface Props {
|
||||
config: Config;
|
||||
}
|
||||
|
||||
const Header = (props: Props): JSX.Element => {
|
||||
const [dateTime, setDateTime] = useState<string>(getDateTime());
|
||||
const [greeting, setGreeting] = useState<string>(greeter());
|
||||
|
||||
useEffect(() => {
|
||||
let dateTimeInterval: NodeJS.Timeout;
|
||||
|
||||
dateTimeInterval = setInterval(() => {
|
||||
setDateTime(getDateTime());
|
||||
setGreeting(greeter());
|
||||
}, 1000);
|
||||
|
||||
return () => window.clearInterval(dateTimeInterval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<header className={classes.Header}>
|
||||
<p>{dateTime}</p>
|
||||
<Link to="/settings" className={classes.SettingsLink}>
|
||||
Go to Settings
|
||||
</Link>
|
||||
<span className={classes.HeaderMain}>
|
||||
<h1>{greeting}</h1>
|
||||
<WeatherWidget />
|
||||
</span>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(Header);
|
|
@ -1,5 +1,5 @@
|
|||
export const dateTime = (): string => {
|
||||
const days = [
|
||||
export const getDateTime = (): string => {
|
||||
const days = localStorage.getItem('daySchema')?.split(';') || [
|
||||
'Sunday',
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
|
@ -8,7 +8,8 @@ export const dateTime = (): string => {
|
|||
'Friday',
|
||||
'Saturday',
|
||||
];
|
||||
const months = [
|
||||
|
||||
const months = localStorage.getItem('monthSchema')?.split(';') || [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
17
client/src/components/Home/Header/functions/greeter.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
export const greeter = (): string => {
|
||||
const now = new Date().getHours();
|
||||
let msg: string;
|
||||
|
||||
const greetingsSchemaRaw =
|
||||
localStorage.getItem('greetingsSchema') ||
|
||||
'Good evening!;Good afternoon!;Good morning!;Good night!';
|
||||
const greetingsSchema = greetingsSchemaRaw.split(';');
|
||||
|
||||
if (now >= 18) msg = greetingsSchema[0];
|
||||
else if (now >= 12) msg = greetingsSchema[1];
|
||||
else if (now >= 6) msg = greetingsSchema[2];
|
||||
else if (now >= 0) msg = greetingsSchema[3];
|
||||
else msg = 'Hello!';
|
||||
|
||||
return msg;
|
||||
};
|
|
@ -1,24 +1,3 @@
|
|||
.Header h1 {
|
||||
color: var(--color-primary);
|
||||
font-weight: 700;
|
||||
font-size: 4em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.Header p {
|
||||
color: var(--color-primary);
|
||||
font-weight: 300;
|
||||
text-transform: uppercase;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.HeaderMain {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.SettingsButton {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
|
@ -40,19 +19,10 @@
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
.SettingsLink {
|
||||
visibility: visible;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.SettingsButton {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.SettingsLink {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.HomeSpace {
|
||||
|
|
|
@ -21,12 +21,8 @@ import classes from './Home.module.css';
|
|||
// Components
|
||||
import AppGrid from '../Apps/AppGrid/AppGrid';
|
||||
import BookmarkGrid from '../Bookmarks/BookmarkGrid/BookmarkGrid';
|
||||
import WeatherWidget from '../Widgets/WeatherWidget/WeatherWidget';
|
||||
import SearchBar from '../SearchBar/SearchBar';
|
||||
|
||||
// Functions
|
||||
import { greeter } from './functions/greeter';
|
||||
import { dateTime } from './functions/dateTime';
|
||||
import Header from './Header/Header';
|
||||
|
||||
interface ComponentProps {
|
||||
getApps: Function;
|
||||
|
@ -48,11 +44,6 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||
categoriesLoading,
|
||||
} = props;
|
||||
|
||||
const [header, setHeader] = useState({
|
||||
dateTime: dateTime(),
|
||||
greeting: greeter(),
|
||||
});
|
||||
|
||||
// Local search query
|
||||
const [localSearch, setLocalSearch] = useState<null | string>(null);
|
||||
const [appSearchResult, setAppSearchResult] = useState<null | App[]>(null);
|
||||
|
@ -74,23 +65,6 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||
}
|
||||
}, [getCategories]);
|
||||
|
||||
// Refresh greeter and time
|
||||
useEffect(() => {
|
||||
let interval: any;
|
||||
|
||||
// Start interval only when hideHeader is false
|
||||
if (!props.config.hideHeader) {
|
||||
interval = setInterval(() => {
|
||||
setHeader({
|
||||
dateTime: dateTime(),
|
||||
greeting: greeter(),
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (localSearch) {
|
||||
// Search through apps
|
||||
|
@ -126,20 +100,7 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||
<div></div>
|
||||
)}
|
||||
|
||||
{!props.config.hideHeader ? (
|
||||
<header className={classes.Header}>
|
||||
<p>{header.dateTime}</p>
|
||||
<Link to="/settings" className={classes.SettingsLink}>
|
||||
Go to Settings
|
||||
</Link>
|
||||
<span className={classes.HeaderMain}>
|
||||
<h1>{header.greeting}</h1>
|
||||
<WeatherWidget />
|
||||
</span>
|
||||
</header>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
{!props.config.hideHeader ? <Header /> : <div></div>}
|
||||
|
||||
{!props.config.hideApps ? (
|
||||
<Fragment>
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
export const greeter = (): string => {
|
||||
const now = new Date().getHours();
|
||||
let msg: string;
|
||||
|
||||
if (now >= 18) msg = 'Good evening!';
|
||||
else if (now >= 12) msg = 'Good afternoon!';
|
||||
else if (now >= 6) msg = 'Good morning!';
|
||||
else if (now >= 0) msg = 'Good night!';
|
||||
else msg = 'Hello!';
|
||||
|
||||
return msg;
|
||||
}
|
|
@ -91,8 +91,18 @@ const SearchBar = (props: ComponentProps): JSX.Element => {
|
|||
// Local query -> redirect if at least 1 result found
|
||||
if (appSearchResult?.length) {
|
||||
redirectUrl(appSearchResult[0].url, sameTab);
|
||||
} else if (bookmarkSearchResult?.length) {
|
||||
} else if (bookmarkSearchResult?.[0]?.bookmarks?.length) {
|
||||
redirectUrl(bookmarkSearchResult[0].bookmarks[0].url, sameTab);
|
||||
} else {
|
||||
// no local results -> search the internet with the default search provider
|
||||
let template = query.template;
|
||||
|
||||
if (query.prefix === 'l') {
|
||||
template = 'https://duckduckgo.com/?q=';
|
||||
}
|
||||
|
||||
const url = `${template}${search}`;
|
||||
redirectUrl(url, sameTab);
|
||||
}
|
||||
} else {
|
||||
// Valid query -> redirect to search results
|
||||
|
|
|
@ -81,6 +81,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
<form onSubmit={(e) => formSubmitHandler(e)}>
|
||||
{/* OTHER OPTIONS */}
|
||||
<SettingsHeadline text="Miscellaneous" />
|
||||
{/* PAGE TITLE */}
|
||||
<InputGroup>
|
||||
<label htmlFor="customTitle">Custom page title</label>
|
||||
<input
|
||||
|
@ -92,6 +93,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
{/* DATE FORMAT */}
|
||||
<InputGroup>
|
||||
<label htmlFor="useAmericanDate">Date formatting</label>
|
||||
<select
|
||||
|
@ -107,6 +110,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
{/* BEAHVIOR OPTIONS */}
|
||||
<SettingsHeadline text="App Behavior" />
|
||||
{/* PIN APPS */}
|
||||
<InputGroup>
|
||||
<label htmlFor="pinAppsByDefault">
|
||||
Pin new applications by default
|
||||
|
@ -121,6 +125,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
{/* PIN CATEGORIES */}
|
||||
<InputGroup>
|
||||
<label htmlFor="pinCategoriesByDefault">
|
||||
Pin new categories by default
|
||||
|
@ -135,6 +141,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
{/* SORT TYPE */}
|
||||
<InputGroup>
|
||||
<label htmlFor="useOrdering">Sorting type</label>
|
||||
<select
|
||||
|
@ -148,6 +156,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
<option value="orderId">Custom order</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
{/* APPS OPPENING */}
|
||||
<InputGroup>
|
||||
<label htmlFor="appsSameTab">Open applications in the same tab</label>
|
||||
<select
|
||||
|
@ -160,6 +170,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
{/* BOOKMARKS OPPENING */}
|
||||
<InputGroup>
|
||||
<label htmlFor="bookmarksSameTab">Open bookmarks in the same tab</label>
|
||||
<select
|
||||
|
@ -175,6 +187,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
{/* MODULES OPTIONS */}
|
||||
<SettingsHeadline text="Modules" />
|
||||
{/* HIDE HEADER */}
|
||||
<InputGroup>
|
||||
<label htmlFor="hideHeader">Hide greeting and date</label>
|
||||
<select
|
||||
|
@ -187,6 +200,53 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
{/* CUSTOM GREETINGS */}
|
||||
<InputGroup>
|
||||
<label htmlFor="greetingsSchema">Custom greetings</label>
|
||||
<input
|
||||
type="text"
|
||||
id="greetingsSchema"
|
||||
name="greetingsSchema"
|
||||
placeholder="Good day;Hi;Bye!"
|
||||
value={formData.greetingsSchema}
|
||||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
<span>
|
||||
Greetings must be separated with semicolon. Only 4 messages can be
|
||||
used
|
||||
</span>
|
||||
</InputGroup>
|
||||
|
||||
{/* CUSTOM DAYS */}
|
||||
<InputGroup>
|
||||
<label htmlFor="daySchema">Custom weekday names</label>
|
||||
<input
|
||||
type="text"
|
||||
id="daySchema"
|
||||
name="daySchema"
|
||||
placeholder="Sunday;Monday;Tuesday"
|
||||
value={formData.daySchema}
|
||||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
<span>Names must be separated with semicolon</span>
|
||||
</InputGroup>
|
||||
|
||||
{/* CUSTOM MONTHS */}
|
||||
<InputGroup>
|
||||
<label htmlFor="monthSchema">Custom month names</label>
|
||||
<input
|
||||
type="text"
|
||||
id="monthSchema"
|
||||
name="monthSchema"
|
||||
placeholder="January;February;March"
|
||||
value={formData.monthSchema}
|
||||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
<span>Names must be separated with semicolon</span>
|
||||
</InputGroup>
|
||||
|
||||
{/* HIDE APPS */}
|
||||
<InputGroup>
|
||||
<label htmlFor="hideApps">Hide applications</label>
|
||||
<select
|
||||
|
@ -199,6 +259,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
{/* HIDE CATEGORIES */}
|
||||
<InputGroup>
|
||||
<label htmlFor="hideCategories">Hide categories</label>
|
||||
<select
|
||||
|
@ -214,6 +276,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
{/* DOCKER SETTINGS */}
|
||||
<SettingsHeadline text="Docker" />
|
||||
{/* CUSTOM DOCKER SOCKET HOST */}
|
||||
<InputGroup>
|
||||
<label htmlFor="dockerHost">Docker Host</label>
|
||||
<input
|
||||
|
@ -225,6 +288,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
{/* USE DOCKER API */}
|
||||
<InputGroup>
|
||||
<label htmlFor="dockerApps">Use Docker API</label>
|
||||
<select
|
||||
|
@ -237,6 +302,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
{/* UNPIN DOCKER APPS */}
|
||||
<InputGroup>
|
||||
<label htmlFor="unpinStoppedApps">
|
||||
Unpin stopped containers / other apps
|
||||
|
@ -254,6 +321,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
{/* KUBERNETES SETTINGS */}
|
||||
<SettingsHeadline text="Kubernetes" />
|
||||
{/* USE KUBERNETES */}
|
||||
<InputGroup>
|
||||
<label htmlFor="kubernetesApps">Use Kubernetes Ingress API</label>
|
||||
<select
|
||||
|
|
|
@ -95,6 +95,30 @@
|
|||
"primary": "#4C432E",
|
||||
"accent": "#AA9A73"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "neon",
|
||||
"colors": {
|
||||
"background": "#091833",
|
||||
"primary": "#EFFBFF",
|
||||
"accent": "#ea00d9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pumpkin",
|
||||
"colors": {
|
||||
"background": "#2d3436",
|
||||
"primary": "#EFFBFF",
|
||||
"accent": "#ffa500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "onedark",
|
||||
"colors": {
|
||||
"background": "#282c34",
|
||||
"primary": "#dfd9d6",
|
||||
"accent": "#98c379"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -21,4 +21,7 @@ export interface Config {
|
|||
unpinStoppedApps: boolean;
|
||||
useAmericanDate: boolean;
|
||||
disableAutofocus: boolean;
|
||||
greetingsSchema: string;
|
||||
daySchema: string;
|
||||
monthSchema: string;
|
||||
}
|
||||
|
|
|
@ -27,4 +27,7 @@ export interface OtherSettingsForm {
|
|||
kubernetesApps: boolean;
|
||||
unpinStoppedApps: boolean;
|
||||
useAmericanDate: boolean;
|
||||
greetingsSchema: string;
|
||||
daySchema: string;
|
||||
monthSchema: string;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Dispatch } from 'redux';
|
|||
import { ActionTypes } from './actionTypes';
|
||||
import { Config, ApiResponse, Query } from '../../interfaces';
|
||||
import { CreateNotificationAction } from './notification';
|
||||
import { storeUIConfig } from '../../utility';
|
||||
|
||||
export interface GetConfigAction {
|
||||
type: ActionTypes.getConfig;
|
||||
|
@ -22,7 +23,15 @@ export const getConfig = () => async (dispatch: Dispatch) => {
|
|||
document.title = res.data.data.customTitle;
|
||||
|
||||
// Store settings for priority UI elements
|
||||
localStorage.setItem('useAmericanDate', `${res.data.data.useAmericanDate}`);
|
||||
const keys: (keyof Config)[] = [
|
||||
'useAmericanDate',
|
||||
'greetingsSchema',
|
||||
'daySchema',
|
||||
'monthSchema',
|
||||
];
|
||||
for (let key of keys) {
|
||||
storeUIConfig(key, res.data.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
@ -51,7 +60,15 @@ export const updateConfig = (formData: any) => async (dispatch: Dispatch) => {
|
|||
});
|
||||
|
||||
// Store settings for priority UI elements
|
||||
localStorage.setItem('useAmericanDate', `${res.data.data.useAmericanDate}`);
|
||||
const keys: (keyof Config)[] = [
|
||||
'useAmericanDate',
|
||||
'greetingsSchema',
|
||||
'daySchema',
|
||||
'monthSchema',
|
||||
];
|
||||
for (let key of keys) {
|
||||
storeUIConfig(key, res.data.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ export interface State {
|
|||
|
||||
const initialState: State = {
|
||||
loading: true,
|
||||
config: configTemplate,
|
||||
config: { ...configTemplate },
|
||||
customQueries: [],
|
||||
};
|
||||
|
||||
|
|
|
@ -6,3 +6,4 @@ export * from './searchParser';
|
|||
export * from './redirectUrl';
|
||||
export * from './templateObjects';
|
||||
export * from './inputHandler';
|
||||
export * from './storeUIConfig';
|
||||
|
|
8
client/src/utility/storeUIConfig.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { Config } from '../interfaces';
|
||||
|
||||
export const storeUIConfig = <K extends keyof Config>(
|
||||
key: K,
|
||||
config: Config
|
||||
) => {
|
||||
localStorage.setItem(key, `${config[key]}`);
|
||||
};
|
|
@ -23,4 +23,8 @@ export const configTemplate: Config = {
|
|||
unpinStoppedApps: false,
|
||||
useAmericanDate: false,
|
||||
disableAutofocus: false,
|
||||
greetingsSchema: 'Good evening!;Good afternoon!;Good morning!;Good night!',
|
||||
daySchema: 'Sunday;Monday;Tuesday;Wednesday;Thursday;Friday;Saturday',
|
||||
monthSchema:
|
||||
'January;February;March;April;May;June;July;August;September;October;November;December',
|
||||
};
|
||||
|
|
|
@ -15,6 +15,10 @@ export const otherSettingsTemplate: OtherSettingsForm = {
|
|||
kubernetesApps: true,
|
||||
unpinStoppedApps: true,
|
||||
useAmericanDate: false,
|
||||
greetingsSchema: 'Good evening!;Good afternoon!;Good morning!;Good night!',
|
||||
daySchema: 'Sunday;Monday;Tuesday;Wednesday;Thursday;Friday;Saturday',
|
||||
monthSchema:
|
||||
'January;February;March;April;May;June;July;August;September;October;November;December',
|
||||
};
|
||||
|
||||
export const weatherSettingsTemplate: WeatherForm = {
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
const queries = require('../../src/utility/searchQueries.json');
|
||||
const fs = require('fs');
|
||||
const prettier = require('prettier');
|
||||
|
||||
/**
|
||||
* @description CLI tool for adding new search engines/providers. It will ensure that prefix is unique and that all entries are sorted alphabetically
|
||||
* @argumens name prefix template
|
||||
* @example node cli-searchQueries.js "DuckDuckGo" "d" "https://duckduckgo.com/?q="
|
||||
*/
|
||||
|
||||
// Get arguments
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
// Check arguments
|
||||
if (args.length < 3) {
|
||||
return console.log('Missing arguments');
|
||||
} else if (args.length > 3) {
|
||||
return console.log('Too many arguments provided');
|
||||
}
|
||||
|
||||
// Construct new query object
|
||||
const newQuery = {
|
||||
name: args[0],
|
||||
prefix: args[1],
|
||||
template: args[2],
|
||||
};
|
||||
|
||||
// Get old queries
|
||||
let rawQueries = queries.queries;
|
||||
let parsedQueries = '';
|
||||
|
||||
// Check if prefix is unique
|
||||
const isUnique = !rawQueries.find((query) => query.prefix == newQuery.prefix);
|
||||
|
||||
if (!isUnique) {
|
||||
return console.log('Prefix already exists');
|
||||
}
|
||||
|
||||
// Add new query
|
||||
rawQueries.push(newQuery);
|
||||
|
||||
// Sort alphabetically
|
||||
rawQueries = rawQueries.sort((a, b) => {
|
||||
const _a = a.name.toLowerCase();
|
||||
const _b = b.name.toLowerCase();
|
||||
|
||||
if (_a < _b) return -1;
|
||||
if (_a > _b) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Format JSON
|
||||
parsedQueries = JSON.stringify(queries);
|
||||
parsedQueries = prettier.format(parsedQueries, { parser: 'json' });
|
||||
|
||||
// Save file
|
||||
fs.writeFileSync('../../src/utility/searchQueries.json', parsedQueries);
|
28
controllers/categories/createCategory.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const Category = require('../../models/Category');
|
||||
const loadConfig = require('../../utils/loadConfig');
|
||||
|
||||
// @desc Create new category
|
||||
// @route POST /api/categories
|
||||
// @access Public
|
||||
const createCategory = asyncWrapper(async (req, res, next) => {
|
||||
const { pinCategoriesByDefault: pinCategories } = await loadConfig();
|
||||
|
||||
let category;
|
||||
|
||||
if (pinCategories) {
|
||||
category = await Category.create({
|
||||
...req.body,
|
||||
isPinned: true,
|
||||
});
|
||||
} else {
|
||||
category = await Category.create(req.body);
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: category,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = createCategory;
|
45
controllers/categories/deleteCategory.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const ErrorResponse = require('../../utils/ErrorResponse');
|
||||
const Category = require('../../models/Category');
|
||||
const Bookmark = require('../../models/Bookmark');
|
||||
|
||||
// @desc Delete category
|
||||
// @route DELETE /api/categories/:id
|
||||
// @access Public
|
||||
const deleteCategory = asyncWrapper(async (req, res, next) => {
|
||||
const category = await Category.findOne({
|
||||
where: { id: req.params.id },
|
||||
include: [
|
||||
{
|
||||
model: Bookmark,
|
||||
as: 'bookmarks',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!category) {
|
||||
return next(
|
||||
new ErrorResponse(
|
||||
`Category with id of ${req.params.id} was not found`,
|
||||
404
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
category.bookmarks.forEach(async (bookmark) => {
|
||||
await Bookmark.destroy({
|
||||
where: { id: bookmark.id },
|
||||
});
|
||||
});
|
||||
|
||||
await Category.destroy({
|
||||
where: { id: req.params.id },
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = deleteCategory;
|
43
controllers/categories/getAllCategories.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const Category = require('../../models/Category');
|
||||
const Bookmark = require('../../models/Bookmark');
|
||||
const { Sequelize } = require('sequelize');
|
||||
const loadConfig = require('../../utils/loadConfig');
|
||||
|
||||
// @desc Get all categories
|
||||
// @route GET /api/categories
|
||||
// @access Public
|
||||
const getAllCategories = asyncWrapper(async (req, res, next) => {
|
||||
const { useOrdering: orderType } = await loadConfig();
|
||||
|
||||
let categories;
|
||||
|
||||
if (orderType == 'name') {
|
||||
categories = await Category.findAll({
|
||||
include: [
|
||||
{
|
||||
model: Bookmark,
|
||||
as: 'bookmarks',
|
||||
},
|
||||
],
|
||||
order: [[Sequelize.fn('lower', Sequelize.col('Category.name')), 'ASC']],
|
||||
});
|
||||
} else {
|
||||
categories = await Category.findAll({
|
||||
include: [
|
||||
{
|
||||
model: Bookmark,
|
||||
as: 'bookmarks',
|
||||
},
|
||||
],
|
||||
order: [[orderType, 'ASC']],
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: categories,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = getAllCategories;
|
35
controllers/categories/getSingleCategory.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const ErrorResponse = require('../../utils/ErrorResponse');
|
||||
const Category = require('../../models/Category');
|
||||
const Bookmark = require('../../models/Bookmark');
|
||||
|
||||
// @desc Get single category
|
||||
// @route GET /api/categories/:id
|
||||
// @access Public
|
||||
const getSingleCategory = asyncWrapper(async (req, res, next) => {
|
||||
const category = await Category.findOne({
|
||||
where: { id: req.params.id },
|
||||
include: [
|
||||
{
|
||||
model: Bookmark,
|
||||
as: 'bookmarks',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!category) {
|
||||
return next(
|
||||
new ErrorResponse(
|
||||
`Category with id of ${req.params.id} was not found`,
|
||||
404
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: category,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = getSingleCategory;
|
8
controllers/categories/index.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
createCategory: require('./createCategory'),
|
||||
getAllCategories: require('./getAllCategories'),
|
||||
getSingleCategory: require('./getSingleCategory'),
|
||||
updateCategory: require('./updateCategory'),
|
||||
deleteCategory: require('./deleteCategory'),
|
||||
reorderCategories: require('./reorderCategories'),
|
||||
};
|
22
controllers/categories/reorderCategories.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const Category = require('../../models/Category');
|
||||
// @desc Reorder categories
|
||||
// @route PUT /api/categories/0/reorder
|
||||
// @access Public
|
||||
const reorderCategories = asyncWrapper(async (req, res, next) => {
|
||||
req.body.categories.forEach(async ({ id, orderId }) => {
|
||||
await Category.update(
|
||||
{ orderId },
|
||||
{
|
||||
where: { id },
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = reorderCategories;
|
30
controllers/categories/updateCategory.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const ErrorResponse = require('../../utils/ErrorResponse');
|
||||
const Category = require('../../models/Category');
|
||||
|
||||
// @desc Update category
|
||||
// @route PUT /api/categories/:id
|
||||
// @access Public
|
||||
const updateCategory = asyncWrapper(async (req, res, next) => {
|
||||
let category = await Category.findOne({
|
||||
where: { id: req.params.id },
|
||||
});
|
||||
|
||||
if (!category) {
|
||||
return next(
|
||||
new ErrorResponse(
|
||||
`Category with id of ${req.params.id} was not found`,
|
||||
404
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
category = await category.update({ ...req.body });
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: category,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = updateCategory;
|
|
@ -1,178 +0,0 @@
|
|||
const asyncWrapper = require('../middleware/asyncWrapper');
|
||||
const ErrorResponse = require('../utils/ErrorResponse');
|
||||
const Category = require('../models/Category');
|
||||
const Bookmark = require('../models/Bookmark');
|
||||
const Config = require('../models/Config');
|
||||
const { Sequelize } = require('sequelize');
|
||||
const loadConfig = require('../utils/loadConfig');
|
||||
|
||||
// @desc Create new category
|
||||
// @route POST /api/categories
|
||||
// @access Public
|
||||
exports.createCategory = asyncWrapper(async (req, res, next) => {
|
||||
const { pinCategoriesByDefault: pinCategories } = await loadConfig();
|
||||
|
||||
let category;
|
||||
|
||||
if (pinCategories) {
|
||||
category = await Category.create({
|
||||
...req.body,
|
||||
isPinned: true,
|
||||
});
|
||||
} else {
|
||||
category = await Category.create(req.body);
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: category,
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Get all categories
|
||||
// @route GET /api/categories
|
||||
// @access Public
|
||||
exports.getCategories = asyncWrapper(async (req, res, next) => {
|
||||
const { useOrdering: orderType } = await loadConfig();
|
||||
|
||||
let categories;
|
||||
|
||||
if (orderType == 'name') {
|
||||
categories = await Category.findAll({
|
||||
include: [
|
||||
{
|
||||
model: Bookmark,
|
||||
as: 'bookmarks',
|
||||
},
|
||||
],
|
||||
order: [[Sequelize.fn('lower', Sequelize.col('Category.name')), 'ASC']],
|
||||
});
|
||||
} else {
|
||||
categories = await Category.findAll({
|
||||
include: [
|
||||
{
|
||||
model: Bookmark,
|
||||
as: 'bookmarks',
|
||||
},
|
||||
],
|
||||
order: [[orderType, 'ASC']],
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: categories,
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Get single category
|
||||
// @route GET /api/categories/:id
|
||||
// @access Public
|
||||
exports.getCategory = asyncWrapper(async (req, res, next) => {
|
||||
const category = await Category.findOne({
|
||||
where: { id: req.params.id },
|
||||
include: [
|
||||
{
|
||||
model: Bookmark,
|
||||
as: 'bookmarks',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!category) {
|
||||
return next(
|
||||
new ErrorResponse(
|
||||
`Category with id of ${req.params.id} was not found`,
|
||||
404
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: category,
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Update category
|
||||
// @route PUT /api/categories/:id
|
||||
// @access Public
|
||||
exports.updateCategory = asyncWrapper(async (req, res, next) => {
|
||||
let category = await Category.findOne({
|
||||
where: { id: req.params.id },
|
||||
});
|
||||
|
||||
if (!category) {
|
||||
return next(
|
||||
new ErrorResponse(
|
||||
`Category with id of ${req.params.id} was not found`,
|
||||
404
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
category = await category.update({ ...req.body });
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: category,
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Delete category
|
||||
// @route DELETE /api/categories/:id
|
||||
// @access Public
|
||||
exports.deleteCategory = asyncWrapper(async (req, res, next) => {
|
||||
const category = await Category.findOne({
|
||||
where: { id: req.params.id },
|
||||
include: [
|
||||
{
|
||||
model: Bookmark,
|
||||
as: 'bookmarks',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!category) {
|
||||
return next(
|
||||
new ErrorResponse(
|
||||
`Category with id of ${req.params.id} was not found`,
|
||||
404
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
category.bookmarks.forEach(async (bookmark) => {
|
||||
await Bookmark.destroy({
|
||||
where: { id: bookmark.id },
|
||||
});
|
||||
});
|
||||
|
||||
await Category.destroy({
|
||||
where: { id: req.params.id },
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Reorder categories
|
||||
// @route PUT /api/categories/0/reorder
|
||||
// @access Public
|
||||
exports.reorderCategories = asyncWrapper(async (req, res, next) => {
|
||||
req.body.categories.forEach(async ({ id, orderId }) => {
|
||||
await Category.update(
|
||||
{ orderId },
|
||||
{
|
||||
where: { id },
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {},
|
||||
});
|
||||
});
|
21
controllers/queries/addQuery.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const File = require('../../utils/File');
|
||||
|
||||
// @desc Add custom search query
|
||||
// @route POST /api/queries
|
||||
// @access Public
|
||||
const addQuery = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File('data/customQueries.json');
|
||||
let content = JSON.parse(file.read());
|
||||
|
||||
// Add new query
|
||||
content.queries.push(req.body);
|
||||
file.write(content, true);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: req.body,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = addQuery;
|
22
controllers/queries/deleteQuery.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const File = require('../../utils/File');
|
||||
|
||||
// @desc Delete query
|
||||
// @route DELETE /api/queries/:prefix
|
||||
// @access Public
|
||||
const deleteQuery = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File('data/customQueries.json');
|
||||
let content = JSON.parse(file.read());
|
||||
|
||||
content.queries = content.queries.filter(
|
||||
(q) => q.prefix != req.params.prefix
|
||||
);
|
||||
file.write(content, true);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: content.queries,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = deleteQuery;
|
17
controllers/queries/getQueries.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const File = require('../../utils/File');
|
||||
|
||||
// @desc Get custom queries file
|
||||
// @route GET /api/queries
|
||||
// @access Public
|
||||
const getQueries = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File('data/customQueries.json');
|
||||
const content = JSON.parse(file.read());
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: content.queries,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = getQueries;
|
|
@ -1,81 +1,6 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const File = require('../../utils/File');
|
||||
const { join } = require('path');
|
||||
|
||||
const QUERIES_PATH = join(__dirname, '../../data/customQueries.json');
|
||||
|
||||
// @desc Add custom search query
|
||||
// @route POST /api/queries
|
||||
// @access Public
|
||||
exports.addQuery = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File(QUERIES_PATH);
|
||||
let content = JSON.parse(file.read());
|
||||
|
||||
// Add new query
|
||||
content.queries.push(req.body);
|
||||
file.write(content, true);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: req.body,
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Get custom queries file
|
||||
// @route GET /api/queries
|
||||
// @access Public
|
||||
exports.getQueries = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File(QUERIES_PATH);
|
||||
const content = JSON.parse(file.read());
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: content.queries,
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Update query
|
||||
// @route PUT /api/queries/:prefix
|
||||
// @access Public
|
||||
exports.updateQuery = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File(QUERIES_PATH);
|
||||
let content = JSON.parse(file.read());
|
||||
|
||||
let queryIdx = content.queries.findIndex(
|
||||
(q) => q.prefix == req.params.prefix
|
||||
);
|
||||
|
||||
// query found
|
||||
if (queryIdx > -1) {
|
||||
content.queries = [
|
||||
...content.queries.slice(0, queryIdx),
|
||||
req.body,
|
||||
...content.queries.slice(queryIdx + 1),
|
||||
];
|
||||
}
|
||||
|
||||
file.write(content, true);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: content.queries,
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Delete query
|
||||
// @route DELETE /api/queries/:prefix
|
||||
// @access Public
|
||||
exports.deleteQuery = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File(QUERIES_PATH);
|
||||
let content = JSON.parse(file.read());
|
||||
|
||||
content.queries = content.queries.filter(
|
||||
(q) => q.prefix != req.params.prefix
|
||||
);
|
||||
file.write(content, true);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: content.queries,
|
||||
});
|
||||
});
|
||||
module.exports = {
|
||||
addQuery: require('./addQuery'),
|
||||
getQueries: require('./getQueries'),
|
||||
updateQuery: require('./updateQuery'),
|
||||
deleteQuery: require('./deleteQuery'),
|
||||
};
|
||||
|
|
32
controllers/queries/updateQuery.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const File = require('../../utils/File');
|
||||
|
||||
// @desc Update query
|
||||
// @route PUT /api/queries/:prefix
|
||||
// @access Public
|
||||
const updateQuery = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File('data/customQueries.json');
|
||||
let content = JSON.parse(file.read());
|
||||
|
||||
let queryIdx = content.queries.findIndex(
|
||||
(q) => q.prefix == req.params.prefix
|
||||
);
|
||||
|
||||
// query found
|
||||
if (queryIdx > -1) {
|
||||
content.queries = [
|
||||
...content.queries.slice(0, queryIdx),
|
||||
req.body,
|
||||
...content.queries.slice(queryIdx + 1),
|
||||
];
|
||||
}
|
||||
|
||||
file.write(content, true);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: content.queries,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = updateQuery;
|
|
@ -1,31 +0,0 @@
|
|||
const asyncWrapper = require('../middleware/asyncWrapper');
|
||||
const ErrorResponse = require('../utils/ErrorResponse');
|
||||
const Weather = require('../models/Weather');
|
||||
const getExternalWeather = require('../utils/getExternalWeather');
|
||||
|
||||
// @desc Get latest weather status
|
||||
// @route GET /api/weather
|
||||
// @access Public
|
||||
exports.getWeather = asyncWrapper(async (req, res, next) => {
|
||||
const weather = await Weather.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: weather,
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Update weather
|
||||
// @route GET /api/weather/update
|
||||
// @access Public
|
||||
exports.updateWeather = asyncWrapper(async (req, res, next) => {
|
||||
const weather = await getExternalWeather();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: weather,
|
||||
});
|
||||
});
|
19
controllers/weather/getWather.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const Weather = require('../../models/Weather');
|
||||
|
||||
// @desc Get latest weather status
|
||||
// @route GET /api/weather
|
||||
// @access Public
|
||||
const getWeather = asyncWrapper(async (req, res, next) => {
|
||||
const weather = await Weather.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: weather,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = getWeather;
|
4
controllers/weather/index.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
getWeather: require('./getWather'),
|
||||
updateWeather: require('./updateWeather'),
|
||||
};
|
16
controllers/weather/updateWeather.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const getExternalWeather = require('../../utils/getExternalWeather');
|
||||
|
||||
// @desc Update weather
|
||||
// @route GET /api/weather/update
|
||||
// @access Public
|
||||
const updateWeather = asyncWrapper(async (req, res, next) => {
|
||||
const weather = await getExternalWeather();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: weather,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = updateWeather;
|
734
package-lock.json
generated
18
package.json
|
@ -17,21 +17,21 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@kubernetes/client-node": "^0.15.0",
|
||||
"@types/express": "^4.17.11",
|
||||
"axios": "^0.21.1",
|
||||
"@kubernetes/client-node": "^0.15.1",
|
||||
"@types/express": "^4.17.13",
|
||||
"axios": "^0.24.0",
|
||||
"colors": "^1.4.0",
|
||||
"concurrently": "^6.0.2",
|
||||
"dotenv": "^9.0.0",
|
||||
"concurrently": "^6.3.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^4.17.1",
|
||||
"multer": "^1.4.2",
|
||||
"multer": "^1.4.3",
|
||||
"node-schedule": "^2.0.0",
|
||||
"sequelize": "^6.6.2",
|
||||
"sequelize": "^6.9.0",
|
||||
"sqlite3": "^5.0.2",
|
||||
"umzug": "^2.3.0",
|
||||
"ws": "^7.4.6"
|
||||
"ws": "^8.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.7"
|
||||
"nodemon": "^2.0.14"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,26 +3,21 @@ const router = express.Router();
|
|||
|
||||
const {
|
||||
createCategory,
|
||||
getCategories,
|
||||
getCategory,
|
||||
getAllCategories,
|
||||
getSingleCategory,
|
||||
updateCategory,
|
||||
deleteCategory,
|
||||
reorderCategories
|
||||
} = require('../controllers/category');
|
||||
reorderCategories,
|
||||
} = require('../controllers/categories');
|
||||
|
||||
router
|
||||
.route('/')
|
||||
.post(createCategory)
|
||||
.get(getCategories);
|
||||
router.route('/').post(createCategory).get(getAllCategories);
|
||||
|
||||
router
|
||||
.route('/:id')
|
||||
.get(getCategory)
|
||||
.get(getSingleCategory)
|
||||
.put(updateCategory)
|
||||
.delete(deleteCategory);
|
||||
|
||||
router
|
||||
.route('/0/reorder')
|
||||
.put(reorderCategories);
|
||||
router.route('/0/reorder').put(reorderCategories);
|
||||
|
||||
module.exports = router;
|
|
@ -1,7 +1,7 @@
|
|||
class ErrorResponse extends Error {
|
||||
constructor(message, statusCode) {
|
||||
super(message);
|
||||
this.statusCode = statusCode
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,23 +2,28 @@ const { Op } = require('sequelize');
|
|||
const Weather = require('../models/Weather');
|
||||
const Logger = require('./Logger');
|
||||
const logger = new Logger();
|
||||
const loadConfig = require('./loadConfig');
|
||||
|
||||
const clearWeatherData = async () => {
|
||||
const { WEATHER_API_KEY: secret } = await loadConfig();
|
||||
|
||||
const weather = await Weather.findOne({
|
||||
order: [[ 'createdAt', 'DESC' ]]
|
||||
order: [['createdAt', 'DESC']],
|
||||
});
|
||||
|
||||
if (weather) {
|
||||
await Weather.destroy({
|
||||
where: {
|
||||
id: {
|
||||
[Op.lt]: weather.id
|
||||
}
|
||||
}
|
||||
})
|
||||
[Op.lt]: weather.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (secret) {
|
||||
logger.log('Old weather data was deleted');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = clearWeatherData;
|
|
@ -20,5 +20,8 @@
|
|||
"kubernetesApps": false,
|
||||
"unpinStoppedApps": false,
|
||||
"useAmericanDate": false,
|
||||
"disableAutofocus": false
|
||||
"disableAutofocus": false,
|
||||
"greetingsSchema": "Good evening!;Good afternoon!;Good morning!;Good night!",
|
||||
"daySchema": "Sunday;Monday;Tuesday;Wednesday;Thursday;Friday;Saturday",
|
||||
"monthSchema": "January;February;March;April;May;June;July;August;September;October;November;December"
|
||||
}
|
||||
|
|