diff --git a/client/src/api.ts b/client/src/api.ts
index df4093c..d1cb334 100644
--- a/client/src/api.ts
+++ b/client/src/api.ts
@@ -331,17 +331,17 @@ export interface RegisterUserResponse { }
export function checkPassword(password: string): boolean {
const result = (password.length < 32) &&
(password.length >= 8) &&
- /^[\x00-\x7F]*$/.test(password) && // is ASCII
+ password.split("").every(char => char.charCodeAt(0) <= 127) && // is ASCII
(/[a-z]/g.test(password)) && // has lowercase
(/[A-Z]/g.test(password)) && // has uppercase
(/[0-9]/g.test(password)) && // has number
- (/[!@#\$%\^&\*\(\)_\+/]/g.test(password)) // has special character;
+ (/[!@#$%^&*()_+/]/g.test(password)) // has special character;
console.log("Password check for ", password, ": ", result);
return result;
}
export function checkEmail(email: string): boolean {
- const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ const re = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
const result = re.test(String(email).toLowerCase());
console.log("Email check for ", email, ": ", result);
return result;
diff --git a/client/src/components/MainWindow.tsx b/client/src/components/MainWindow.tsx
index 725f24b..2241615 100644
--- a/client/src/components/MainWindow.tsx
+++ b/client/src/components/MainWindow.tsx
@@ -1,19 +1,17 @@
-import React, { useReducer, Reducer, useContext } from 'react';
+import React from 'react';
import { ThemeProvider, CssBaseline, createMuiTheme } from '@material-ui/core';
import { grey } from '@material-ui/core/colors';
import AppBar, { AppBarTab } from './appbar/AppBar';
-import QueryWindow, { QueryWindowReducer } from './windows/query/QueryWindow';
-import { newWindowState, newWindowReducer, WindowType } from './windows/Windows';
+import QueryWindow from './windows/query/QueryWindow';
import ArtistWindow from './windows/artist/ArtistWindow';
import AlbumWindow from './windows/album/AlbumWindow';
import TagWindow from './windows/tag/TagWindow';
import SongWindow from './windows/song/SongWindow';
import ManageTagsWindow from './windows/manage_tags/ManageTagsWindow';
-import { BrowserRouter, Switch, Route, useParams, Redirect } from 'react-router-dom';
+import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom';
import LoginWindow from './windows/login/LoginWindow';
import { useAuth } from '../lib/useAuth';
import RegisterWindow from './windows/register/RegisterWindow';
-var _ = require('lodash');
const darkTheme = createMuiTheme({
palette: {
diff --git a/client/src/components/appbar/AppBar.tsx b/client/src/components/appbar/AppBar.tsx
index 07e113c..b39601f 100644
--- a/client/src/components/appbar/AppBar.tsx
+++ b/client/src/components/appbar/AppBar.tsx
@@ -1,10 +1,8 @@
import React from 'react';
import { AppBar as MuiAppBar, Box, Tab as MuiTab, Tabs, IconButton, Typography, Menu, MenuItem } from '@material-ui/core';
-import CloseIcon from '@material-ui/icons/Close';
import SearchIcon from '@material-ui/icons/Search';
import LocalOfferIcon from '@material-ui/icons/LocalOffer';
import { Link, useHistory } from 'react-router-dom';
-import { WindowType } from '../windows/Windows';
import { useAuth } from '../../lib/useAuth';
export enum AppBarTab {
diff --git a/client/src/components/querybuilder/QBLeafElem.tsx b/client/src/components/querybuilder/QBLeafElem.tsx
index 6398b7e..dad3d03 100644
--- a/client/src/components/querybuilder/QBLeafElem.tsx
+++ b/client/src/components/querybuilder/QBLeafElem.tsx
@@ -110,56 +110,56 @@ export function QBLeafElem(props: IProps) {
: undefined;
- if (e.a == QueryLeafBy.ArtistName &&
- e.leafOp == QueryLeafOp.Equals &&
+ if (e.a === QueryLeafBy.ArtistName &&
+ e.leafOp === QueryLeafOp.Equals &&
typeof e.b == "string") {
return
- } else if (e.a == QueryLeafBy.ArtistName &&
- e.leafOp == QueryLeafOp.Like &&
+ } else if (e.a === QueryLeafBy.ArtistName &&
+ e.leafOp === QueryLeafOp.Like &&
typeof e.b == "string") {
return
- } else if (e.a == QueryLeafBy.AlbumName &&
- e.leafOp == QueryLeafOp.Equals &&
+ } else if (e.a === QueryLeafBy.AlbumName &&
+ e.leafOp === QueryLeafOp.Equals &&
typeof e.b == "string") {
return
- } else if (e.a == QueryLeafBy.AlbumName &&
- e.leafOp == QueryLeafOp.Like &&
+ } else if (e.a === QueryLeafBy.AlbumName &&
+ e.leafOp === QueryLeafOp.Like &&
typeof e.b == "string") {
return
- } if (e.a == QueryLeafBy.SongTitle &&
- e.leafOp == QueryLeafOp.Equals &&
+ } if (e.a === QueryLeafBy.SongTitle &&
+ e.leafOp === QueryLeafOp.Equals &&
typeof e.b == "string") {
return
- } else if (e.a == QueryLeafBy.SongTitle &&
- e.leafOp == QueryLeafOp.Like &&
+ } else if (e.a === QueryLeafBy.SongTitle &&
+ e.leafOp === QueryLeafOp.Like &&
typeof e.b == "string") {
return
- } else if (e.a == QueryLeafBy.TagInfo &&
- e.leafOp == QueryLeafOp.Equals &&
+ } else if (e.a === QueryLeafBy.TagInfo &&
+ e.leafOp === QueryLeafOp.Equals &&
isTagQueryInfo(e.b)) {
return
- }else if (e.leafOp == QueryLeafOp.Placeholder) {
+ }else if (e.leafOp === QueryLeafOp.Placeholder) {
return
});
- if (e.nodeOp == QueryNodeOp.And) {
+ if (e.nodeOp === QueryNodeOp.And) {
return {children}
- } else if (e.nodeOp == QueryNodeOp.Or) {
+ } else if (e.nodeOp === QueryNodeOp.Or) {
return {children}
}
- throw "Unsupported node element";
+ throw new Error("Unsupported node element");
}
\ No newline at end of file
diff --git a/client/src/components/querybuilder/QBSelectWithRequest.tsx b/client/src/components/querybuilder/QBSelectWithRequest.tsx
index 107e982..77d4574 100644
--- a/client/src/components/querybuilder/QBSelectWithRequest.tsx
+++ b/client/src/components/querybuilder/QBSelectWithRequest.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import CircularProgress from '@material-ui/core/CircularProgress';
@@ -26,26 +26,26 @@ export default function QBSelectWithRequest(props: IProps & any) {
const loading: boolean = !options || options.forInput !== input;
- const updateOptions = (forInput: string, options: any[]) => {
+ const updateOptions = useCallback((forInput: string, options: any[]) => {
if (forInput === input) {
setOptions({
forInput: forInput,
options: options,
});
}
- }
+ }, [setOptions, input]);
- const startRequest = (_input: string) => {
+ const startRequest = useCallback((_input: string) => {
setInput(_input);
(async () => {
const newOptions = await getNewOptions(_input);
updateOptions(_input, newOptions);
})();
- };
+ }, [setInput, getNewOptions, updateOptions]);
useEffect(() => {
startRequest(input);
- }, [input]);
+ }, [input, startRequest]);
const onInputChange = (e: any, val: any, reason: any) => {
if (reason === 'reset') {
diff --git a/client/src/components/tables/ResultsTable.tsx b/client/src/components/tables/ResultsTable.tsx
index 5fee3d1..1bdce75 100644
--- a/client/src/components/tables/ResultsTable.tsx
+++ b/client/src/components/tables/ResultsTable.tsx
@@ -50,10 +50,8 @@ export default function SongTable(props: {
const artistNames = props.songGetters.getArtistNames(song);
const artist = stringifyList(artistNames);
const mainArtistId = props.songGetters.getArtistIds(song)[0];
- const mainArtistName = artistNames[0];
const albumNames = props.songGetters.getAlbumNames(song);
const album = stringifyList(albumNames);
- const mainAlbumName = albumNames[0];
const mainAlbumId = props.songGetters.getAlbumIds(song)[0];
const songId = props.songGetters.getId(song);
const tagIds = props.songGetters.getTagIds(song);
diff --git a/client/src/components/windows/album/AlbumWindow.tsx b/client/src/components/windows/album/AlbumWindow.tsx
index 07daa38..7d14329 100644
--- a/client/src/components/windows/album/AlbumWindow.tsx
+++ b/client/src/components/windows/album/AlbumWindow.tsx
@@ -12,7 +12,6 @@ import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import { queryAlbums, querySongs } from '../../../lib/backend/queries';
import { songGetters } from '../../../lib/songGetters';
import { useParams } from 'react-router';
-var _ = require('lodash');
export type AlbumMetadata = serverApi.AlbumDetails;
export type AlbumMetadataChanges = serverApi.ModifyAlbumRequest;
@@ -76,40 +75,40 @@ export function AlbumWindowControlled(props: {
state: AlbumWindowState,
dispatch: (action: any) => void,
}) {
- let metadata = props.state.metadata;
- let pendingChanges = props.state.pendingChanges;
+ let { id: albumId, metadata, pendingChanges, songsOnAlbum } = props.state;
+ let { dispatch } = props;
// Effect to get the album's metadata.
useEffect(() => {
- getAlbumMetadata(props.state.id)
+ getAlbumMetadata(albumId)
.then((m: AlbumMetadata) => {
- props.dispatch({
+ dispatch({
type: AlbumWindowStateActions.SetMetadata,
value: m
});
})
- }, [metadata?.name]);
+ }, [albumId, dispatch]);
// Effect to get the album's songs.
useEffect(() => {
- if (props.state.songsOnAlbum) { return; }
+ if (songsOnAlbum) { return; }
(async () => {
const songs = await querySongs({
query: {
a: QueryLeafBy.AlbumId,
- b: props.state.id,
+ b: albumId,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
limit: -1,
});
- props.dispatch({
+ dispatch({
type: AlbumWindowStateActions.SetSongs,
value: songs,
});
})();
- }, [props.state.songsOnAlbum]);
+ }, [songsOnAlbum, albumId, dispatch]);
const [editingName, setEditingName] = useState(null);
const name = {
const store = whichStore(link);
return store &&
void,
}) {
- let metadata = props.state.metadata;
- let pendingChanges = props.state.pendingChanges;
+ let { metadata, id: artistId, pendingChanges, songsByArtist } = props.state;
+ let { dispatch } = props;
// Effect to get the artist's metadata.
useEffect(() => {
- getArtistMetadata(props.state.id)
+ getArtistMetadata(artistId)
.then((m: ArtistMetadata) => {
- props.dispatch({
+ dispatch({
type: ArtistWindowStateActions.SetMetadata,
value: m
});
})
- }, [metadata?.name]);
+ }, [artistId, dispatch]);
// Effect to get the artist's songs.
useEffect(() => {
- if (props.state.songsByArtist) { return; }
+ if (songsByArtist) { return; }
(async () => {
const songs = await querySongs({
query: {
a: QueryLeafBy.ArtistId,
- b: props.state.id,
+ b: artistId,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
limit: -1,
});
- props.dispatch({
+ dispatch({
type: ArtistWindowStateActions.SetSongs,
value: songs,
});
})();
- }, [props.state.songsByArtist]);
+ }, [songsByArtist, dispatch, artistId]);
const [editingName, setEditingName] = useState(null);
const name = {
const store = whichStore(link);
return store &&
void,
}) {
const [newTagMenuPos, setNewTagMenuPos] = React.useState(null);
+ let { fetchedTags } = props.state;
+ let { dispatch } = props;
const onOpenNewTagMenu = (e: any) => {
setNewTagMenuPos([e.clientX, e.clientY])
@@ -364,19 +364,19 @@ export function ManageTagsWindowControlled(props: {
};
useEffect(() => {
- if (props.state.fetchedTags !== null) {
+ if (fetchedTags !== null) {
return;
}
(async () => {
const allTags = await getAllTags();
// We have the tags in list form. Now, we want to organize
// them hierarchically by giving each tag a "children" prop.
- props.dispatch({
+ dispatch({
type: ManageTagsWindowActions.SetFetchedTags,
value: allTags,
});
})();
- }, [props.state.fetchedTags]);
+ }, [fetchedTags, dispatch]);
const tagsWithChanges = annotateTagsWithChanges(props.state.fetchedTags || {}, props.state.pendingChanges || [])
const changedTags = organiseTags(
diff --git a/client/src/components/windows/manage_tags/NewTagMenu.tsx b/client/src/components/windows/manage_tags/NewTagMenu.tsx
index 3d16315..988ed93 100644
--- a/client/src/components/windows/manage_tags/NewTagMenu.tsx
+++ b/client/src/components/windows/manage_tags/NewTagMenu.tsx
@@ -1,5 +1,5 @@
-import React, { useState } from 'react';
-import { Menu, MenuItem, TextField, Input } from '@material-ui/core';
+import React from 'react';
+import { Menu } from '@material-ui/core';
import NestedMenuItem from "material-ui-nested-menu-item";
import MenuEditText from '../../common/MenuEditText';
diff --git a/client/src/components/windows/manage_tags/TagChange.tsx b/client/src/components/windows/manage_tags/TagChange.tsx
index cce1c06..b2e9c35 100644
--- a/client/src/components/windows/manage_tags/TagChange.tsx
+++ b/client/src/components/windows/manage_tags/TagChange.tsx
@@ -1,7 +1,5 @@
-import React, { useState, useEffect } from 'react';
+import React from 'react';
import { Typography, Chip, CircularProgress, Box, Paper } from '@material-ui/core';
-import { queryTags } from '../../../lib/backend/queries';
-import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import DiscardChangesButton from '../../common/DiscardChangesButton';
import SubmitChangesButton from '../../common/SubmitChangesButton';
import { createTag, modifyTag, deleteTag, mergeTag } from '../../../lib/backend/tags';
@@ -31,7 +29,7 @@ export async function submitTagChanges(changes: TagChange[]) {
var id_lookup: Record = {}
const getId = (id_string: string) => {
- return (Number(id_string) === NaN) ?
+ return (isNaN(Number(id_string))) ?
id_lookup[id_string] : Number(id_string);
}
diff --git a/client/src/components/windows/query/QueryWindow.tsx b/client/src/components/windows/query/QueryWindow.tsx
index 9561883..85afec5 100644
--- a/client/src/components/windows/query/QueryWindow.tsx
+++ b/client/src/components/windows/query/QueryWindow.tsx
@@ -1,24 +1,13 @@
-import React, { useEffect, useReducer } from 'react';
-import { createMuiTheme, Box, LinearProgress } from '@material-ui/core';
-import { QueryElem, toApiQuery, QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
+import React, { useEffect, useReducer, useCallback } from 'react';
+import { Box, LinearProgress } from '@material-ui/core';
+import { QueryElem, QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import QueryBuilder from '../../querybuilder/QueryBuilder';
-import * as serverApi from '../../../api';
import SongTable from '../../tables/ResultsTable';
import { songGetters } from '../../../lib/songGetters';
import { queryArtists, querySongs, queryAlbums, queryTags } from '../../../lib/backend/queries';
-import { grey } from '@material-ui/core/colors';
import { WindowState } from '../Windows';
var _ = require('lodash');
-const darkTheme = createMuiTheme({
- palette: {
- type: 'dark',
- primary: {
- main: grey[100],
- }
- },
-});
-
export interface ResultsForQuery {
for: QueryElem,
results: any[],
@@ -112,23 +101,23 @@ export function QueryWindowControlled(props: {
state: QueryWindowState,
dispatch: (action: any) => void,
}) {
- let query = props.state.query;
- let editing = props.state.editingQuery;
- let resultsFor = props.state.resultsForQuery;
+ let { query, editingQuery: editing, resultsForQuery: resultsFor } = props.state;
+ let { dispatch } = props;
+
let setQuery = (q: QueryElem | null) => {
props.dispatch({ type: QueryWindowStateActions.SetQuery, value: q });
}
let setEditingQuery = (e: boolean) => {
props.dispatch({ type: QueryWindowStateActions.SetEditingQuery, value: e });
}
- let setResultsForQuery = (r: ResultsForQuery | null) => {
- props.dispatch({ type: QueryWindowStateActions.SetResultsForQuery, value: r });
- }
+ let setResultsForQuery = useCallback((r: ResultsForQuery | null) => {
+ dispatch({ type: QueryWindowStateActions.SetResultsForQuery, value: r });
+ }, [ dispatch ]);
const loading = query && (!resultsFor || !_.isEqual(resultsFor.for, query));
- const showResults = (query && resultsFor && query == resultsFor.for) ? resultsFor.results : [];
+ const showResults = (query && resultsFor && query === resultsFor.for) ? resultsFor.results : [];
- const doQuery = async (_query: QueryElem) => {
+ const doQuery = useCallback(async (_query: QueryElem) => {
const songs = await querySongs({
query: _query,
offset: 0,
@@ -141,7 +130,7 @@ export function QueryWindowControlled(props: {
results: songs,
})
}
- }
+ }, [query, setResultsForQuery]);
useEffect(() => {
if (query) {
@@ -149,7 +138,7 @@ export function QueryWindowControlled(props: {
} else {
setResultsForQuery(null);
}
- }, [query]);
+ }, [query, doQuery, setResultsForQuery]);
return
void,
}) {
let history: any = useHistory();
- let location: any = useLocation();
let auth: Auth = useAuth();
- let { from } = location.state || { from: { pathname: "/" } };
const onSubmit = (event: any) => {
event.preventDefault();
diff --git a/client/src/components/windows/song/SongWindow.tsx b/client/src/components/windows/song/SongWindow.tsx
index 8ba8b6a..c0f9e93 100644
--- a/client/src/components/windows/song/SongWindow.tsx
+++ b/client/src/components/windows/song/SongWindow.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState, useReducer } from 'react';
-import { Box, Typography, IconButton, Button, CircularProgress } from '@material-ui/core';
+import { Box, Typography, IconButton, CircularProgress } from '@material-ui/core';
import AudiotrackIcon from '@material-ui/icons/Audiotrack';
import PersonIcon from '@material-ui/icons/Person';
import AlbumIcon from '@material-ui/icons/Album';
@@ -13,7 +13,6 @@ import SubmitChangesButton from '../../common/SubmitChangesButton';
import { saveSongChanges } from '../../../lib/saveChanges';
import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import { querySongs } from '../../../lib/backend/queries';
-import { songGetters } from '../../../lib/songGetters';
import { useParams } from 'react-router';
export type SongMetadata = serverApi.SongDetails;
@@ -71,18 +70,18 @@ export function SongWindowControlled(props: {
state: SongWindowState,
dispatch: (action: any) => void,
}) {
- let metadata = props.state.metadata;
- let pendingChanges = props.state.pendingChanges;
+ let { pendingChanges, metadata, id: songId } = props.state;
+ let { dispatch } = props;
useEffect(() => {
- getSongMetadata(props.state.id)
+ getSongMetadata(songId)
.then((m: SongMetadata) => {
- props.dispatch({
+ dispatch({
type: SongWindowStateActions.SetMetadata,
value: m
});
})
- }, [metadata?.title]);
+ }, [songId, dispatch]);
const [editingTitle, setEditingTitle] = useState(null);
const title = {
const store = whichStore(link);
return store &&
{
- getTagMetadata(props.state.id)
+ getTagMetadata(tagId)
.then((m: TagMetadata) => {
- props.dispatch({
+ dispatch({
type: TagWindowStateActions.SetMetadata,
value: m
});
})
- }, [metadata?.name]);
+ }, [tagId, dispatch]);
// Effect to get the tag's songs.
useEffect(() => {
- if (props.state.songsWithTag) { return; }
+ if (songsWithTag) { return; }
(async () => {
const songs = await querySongs({
query: {
a: QueryLeafBy.TagId,
- b: props.state.id,
+ b: tagId,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
limit: -1,
});
- props.dispatch({
+ dispatch({
type: TagWindowStateActions.SetSongs,
value: songs,
- });
+ });
})();
- }, [props.state.songsWithTag]);
+ }, [songsWithTag, tagId, dispatch]);
const [editingName, setEditingName] = useState(null);
const name =
const fullName =
{metadata?.fullName.map((n: string, i: number) => {
- if (metadata?.fullName && i == metadata?.fullName.length - 1) {
+ if (metadata?.fullName && i === metadata?.fullName.length - 1) {
return name;
} else if (i >= (metadata?.fullName.length || 0) - 1) {
return undefined;
@@ -160,7 +161,7 @@ export function TagWindowControlled(props: {
const storeLinks = metadata?.storeLinks && metadata?.storeLinks.map((link: string) => {
const store = whichStore(link);
return store &&
{
- if (isLeafElem(op) && op.leafOp == QueryLeafOp.Placeholder) {
+ if (isLeafElem(op) && op.leafOp === QueryLeafOp.Placeholder) {
return;
}
const newOp = removePlaceholders(op);
@@ -136,14 +136,14 @@ export function removePlaceholders(q: QueryElem | null): QueryElem | null {
}
})
- if (newOperands.length == 0) {
+ if (newOperands.length === 0) {
return null;
}
- if (newOperands.length == 1) {
+ if (newOperands.length === 1) {
return newOperands[0];
}
return { operands: newOperands, nodeOp: q.nodeOp };
- } else if (q && isLeafElem(q) && q.leafOp == QueryLeafOp.Placeholder) {
+ } else if (q && isLeafElem(q) && q.leafOp === QueryLeafOp.Placeholder) {
return null;
}