mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-22 13:04:19 +00:00
Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
commit
a3f5139b76
14 changed files with 317 additions and 89 deletions
|
@ -37,9 +37,9 @@ app.add_url_rule("/doa", view_func=page_doa, methods=["GET", "POST"])
|
||||||
app.add_url_rule("/api/login", view_func=page_login, methods=["POST"])
|
app.add_url_rule("/api/login", view_func=page_login, methods=["POST"])
|
||||||
app.add_url_rule("/api/user/create", view_func=page_user_create, methods=["POST"])
|
app.add_url_rule("/api/user/create", view_func=page_user_create, methods=["POST"])
|
||||||
app.add_url_rule("/api/user/remove", view_func=page_user_delete, methods=["POST"])
|
app.add_url_rule("/api/user/remove", view_func=page_user_delete, methods=["POST"])
|
||||||
app.add_url_rule("/api/repository/list", view_func=page_repository_list, methods=["POST"])
|
app.add_url_rule("/api/repository/list", view_func=page_repository_list, methods=["GET"])
|
||||||
app.add_url_rule("/api/repository/create", view_func=page_repository_create, methods=["POST"])
|
app.add_url_rule("/api/repository/create", view_func=page_repository_create, methods=["POST"])
|
||||||
app.add_url_rule("/api/repository/edit", view_func=page_repository_edit, methods=["POST"])
|
app.add_url_rule("/api/repository/edit", view_func=page_repository_edit, methods=["PUT"])
|
||||||
app.add_url_rule("/api/repository/add_condition", view_func=page_repository_add_condition, methods=["POST"])
|
app.add_url_rule("/api/repository/add_condition", view_func=page_repository_add_condition, methods=["POST"])
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -10,18 +10,20 @@ from flask_cors import cross_origin
|
||||||
def page_repository_list():
|
def page_repository_list():
|
||||||
"""
|
"""
|
||||||
API call that returns the list of repositories.
|
API call that returns the list of repositories.
|
||||||
|
|
||||||
:parameter onlyActive: if present, only active repos are provided
|
:parameter onlyActive: if present, only active repos are provided
|
||||||
:parameter onlyDead: if present, only dead repos are provided
|
:parameter onlyDead: if present, only dead repos are provided
|
||||||
|
|
||||||
:returns: a JSON-formatted string that contains under the "content" field the list of repositories that belong to
|
:returns: a JSON-formatted string that contains under the "content" field the list of repositories that belong to
|
||||||
the user ("owner") and a list of repositories that he can spectate ("spectator").
|
the user ("owner") and a list of repositories that he can spectate ("spectator").
|
||||||
"""
|
"""
|
||||||
user = find_user(get_jwt_identity())
|
user = find_user(get_jwt_identity())
|
||||||
owner = Repository.query.filter_by(owner_id=user.email)
|
owner = Repository.query.filter_by(owner_id=user.email)
|
||||||
spectator = Authorization.query.filter_by(email=user.email).join(Repository)
|
spectator = Authorization.query.filter_by(email=user.email).join(Repository)
|
||||||
if request.json.get("onlyActive"):
|
if request.args.get("onlyActive"):
|
||||||
owner = owner.filter_by(isActive=True)
|
owner = owner.filter_by(isActive=True)
|
||||||
spectator = spectator.filter(Repository.isActive == True)
|
spectator = spectator.filter(Repository.isActive == True)
|
||||||
elif request.json.get("onlyDead"):
|
elif request.args.get("onlyDead"):
|
||||||
owner = owner.filter_by(isActive=False)
|
owner = owner.filter_by(isActive=False)
|
||||||
spectator = spectator.filter(Repository.isActive == False)
|
spectator = spectator.filter(Repository.isActive == False)
|
||||||
owner = owner.all()
|
owner = owner.all()
|
||||||
|
|
|
@ -11,6 +11,7 @@ import PageRoot from "./routes/PageRoot"
|
||||||
import GlobalTheme from "./components/GlobalTheme"
|
import GlobalTheme from "./components/GlobalTheme"
|
||||||
import GlobalServer from "./components/GlobalServer"
|
import GlobalServer from "./components/GlobalServer"
|
||||||
import GlobalUser from "./components/GlobalUser"
|
import GlobalUser from "./components/GlobalUser"
|
||||||
|
import PageSwitcher from "./PageSwitcher"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,33 +26,9 @@ export default function App() {
|
||||||
<GlobalUser>
|
<GlobalUser>
|
||||||
<GlobalTheme>
|
<GlobalTheme>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Switch>
|
<PageSwitcher/>
|
||||||
<Route path={"/login"} exact={true}>
|
|
||||||
<PageLogin/>
|
|
||||||
</Route>
|
|
||||||
<Route path={"/repositories"} exact={true}>
|
|
||||||
<PageRepositories/>
|
|
||||||
</Route>
|
|
||||||
<Route path={"/alerts"} exact={true}>
|
|
||||||
<PageAlerts/>
|
|
||||||
</Route>
|
|
||||||
<Route path={"/settings"} exact={true}>
|
|
||||||
<PageSettings/>
|
|
||||||
</Route>
|
|
||||||
<Route path={"/sandbox"} exact={true}>
|
|
||||||
<PageSandbox/>
|
|
||||||
</Route>
|
|
||||||
<Route path={"/dashboard"} exact={true}>
|
|
||||||
<PageDashboard/>
|
|
||||||
</Route>
|
|
||||||
<Route path={"/"}>
|
|
||||||
<PageRoot/>
|
|
||||||
</Route>
|
|
||||||
</Switch>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</GlobalTheme>
|
</GlobalTheme>
|
||||||
</GlobalUser>
|
</GlobalUser>
|
||||||
|
|
38
code/frontend/src/PageSwitcher.js
Normal file
38
code/frontend/src/PageSwitcher.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import React from "react"
|
||||||
|
import { Route, Switch } from "react-router"
|
||||||
|
import PageLogin from "./routes/PageLogin"
|
||||||
|
import PageRepositories from "./routes/PageRepositories"
|
||||||
|
import PageAlerts from "./routes/PageAlerts"
|
||||||
|
import PageSettings from "./routes/PageSettings"
|
||||||
|
import PageSandbox from "./routes/PageSandbox"
|
||||||
|
import PageDashboard from "./routes/PageDashboard"
|
||||||
|
import PageRoot from "./routes/PageRoot"
|
||||||
|
|
||||||
|
|
||||||
|
export default function PageSwitcher({ ...props }) {
|
||||||
|
return (
|
||||||
|
<Switch {...props}>
|
||||||
|
<Route path={"/login"} exact={true}>
|
||||||
|
<PageLogin/>
|
||||||
|
</Route>
|
||||||
|
<Route path={"/repositories"} exact={true}>
|
||||||
|
<PageRepositories/>
|
||||||
|
</Route>
|
||||||
|
<Route path={"/alerts"} exact={true}>
|
||||||
|
<PageAlerts/>
|
||||||
|
</Route>
|
||||||
|
<Route path={"/settings"} exact={true}>
|
||||||
|
<PageSettings/>
|
||||||
|
</Route>
|
||||||
|
<Route path={"/sandbox"} exact={true}>
|
||||||
|
<PageSandbox/>
|
||||||
|
</Route>
|
||||||
|
<Route path={"/dashboard"} exact={true}>
|
||||||
|
<PageDashboard/>
|
||||||
|
</Route>
|
||||||
|
<Route path={"/"}>
|
||||||
|
<PageRoot/>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
|
}
|
38
code/frontend/src/components/BoxRepositoriesActive.js
Normal file
38
code/frontend/src/components/BoxRepositoriesActive.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { useContext } from "react"
|
||||||
|
import BoxFull from "./BoxFull"
|
||||||
|
import ContextUser from "../contexts/ContextUser"
|
||||||
|
import useData from "../hooks/useData"
|
||||||
|
import RepositorySummaryBase from "./RepositorySummaryBase"
|
||||||
|
import Loading from "./Loading"
|
||||||
|
import BoxAlert from "./BoxAlert"
|
||||||
|
|
||||||
|
|
||||||
|
export default function BoxRepositoriesActive({ ...props }) {
|
||||||
|
const {fetchDataAuth} = useContext(ContextUser)
|
||||||
|
const {data, started, loading, error} = useData(fetchDataAuth, "GET", "/api/repository/list", {
|
||||||
|
"onlyAlive": true,
|
||||||
|
})
|
||||||
|
|
||||||
|
let contents;
|
||||||
|
if(!started || loading) {
|
||||||
|
contents = <Loading/>
|
||||||
|
}
|
||||||
|
else if(error) {
|
||||||
|
contents = <BoxAlert color={"Red"}>{error.toString()}</BoxAlert>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let repositories = [...data["owner"], ...data["spectator"]]
|
||||||
|
if(repositories.length > 0) {
|
||||||
|
contents = repositories.map(repo => <RepositorySummaryBase {...repo}/>)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
contents = <i>There's nothing here.</i>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BoxFull header={"Your active repositories"} {...props}>
|
||||||
|
{contents}
|
||||||
|
</BoxFull>
|
||||||
|
)
|
||||||
|
}
|
38
code/frontend/src/components/BoxRepositoriesArchived.js
Normal file
38
code/frontend/src/components/BoxRepositoriesArchived.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { useContext } from "react"
|
||||||
|
import BoxFull from "./BoxFull"
|
||||||
|
import ContextUser from "../contexts/ContextUser"
|
||||||
|
import useData from "../hooks/useData"
|
||||||
|
import RepositorySummaryBase from "./RepositorySummaryBase"
|
||||||
|
import Loading from "./Loading"
|
||||||
|
import BoxAlert from "./BoxAlert"
|
||||||
|
|
||||||
|
|
||||||
|
export default function BoxRepositoriesArchived({ ...props }) {
|
||||||
|
const {fetchDataAuth} = useContext(ContextUser)
|
||||||
|
const {data, started, loading, error} = useData(fetchDataAuth, "GET", "/api/repository/list", {
|
||||||
|
"onlyDead": true,
|
||||||
|
})
|
||||||
|
|
||||||
|
let contents;
|
||||||
|
if(!started || loading) {
|
||||||
|
contents = <Loading/>
|
||||||
|
}
|
||||||
|
else if(error) {
|
||||||
|
contents = <BoxAlert color={"Red"}>{error.toString()}</BoxAlert>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let repositories = [...data["owner"], ...data["spectator"]]
|
||||||
|
if(repositories.length > 0) {
|
||||||
|
contents = repositories.map(repo => <RepositorySummaryBase {...repo}/>)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
contents = <i>There's nothing here.</i>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BoxFull header={"Your archived repositories"} {...props}>
|
||||||
|
{contents}
|
||||||
|
</BoxFull>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import React from "react"
|
import React, { useCallback } from "react"
|
||||||
import useLocalStorageState from "../hooks/useLocalStorageState"
|
import useLocalStorageState from "../hooks/useLocalStorageState"
|
||||||
import ContextServer from "../contexts/ContextServer"
|
import ContextServer from "../contexts/ContextServer"
|
||||||
|
import isString from "is-string"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +25,7 @@ export default function GlobalServer({ children }) {
|
||||||
* @param init - Additional arguments to pass to the `init` parameter of {@link fetch}.
|
* @param init - Additional arguments to pass to the `init` parameter of {@link fetch}.
|
||||||
* @returns {Promise<*>}
|
* @returns {Promise<*>}
|
||||||
*/
|
*/
|
||||||
const fetchData = async (method, path, body, init) => {
|
const fetchData = useCallback(async (method, path, body, init) => {
|
||||||
if(!server) {
|
if(!server) {
|
||||||
throw new Error(`Invalid server: ${server}`)
|
throw new Error(`Invalid server: ${server}`)
|
||||||
}
|
}
|
||||||
|
@ -32,14 +33,31 @@ export default function GlobalServer({ children }) {
|
||||||
if(!init) {
|
if(!init) {
|
||||||
init = {}
|
init = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!init["headers"]) {
|
if(!init["headers"]) {
|
||||||
init["headers"] = {}
|
init["headers"] = {}
|
||||||
}
|
}
|
||||||
init["headers"]["Content-Type"] = "application/json"
|
init["headers"]["Content-Type"] = "application/json"
|
||||||
|
|
||||||
|
if(method.toUpperCase() === "GET" || method.toUpperCase() === "HEAD") {
|
||||||
|
let usp = new URLSearchParams()
|
||||||
|
for(const key in body) {
|
||||||
|
if(!body.hasOwnProperty(key)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const value = body[key]
|
||||||
|
if(!isString(value)) {
|
||||||
|
usp.set(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path += `?${usp.toString()}`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
init["body"] = JSON.stringify(body)
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`${server}${path}`, {
|
const response = await fetch(`${server}${path}`, {
|
||||||
method: method,
|
method: method,
|
||||||
body: JSON.stringify(body),
|
|
||||||
...init,
|
...init,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -50,11 +68,11 @@ export default function GlobalServer({ children }) {
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
|
|
||||||
if(json["result"] !== "success") {
|
if(json["result"] !== "success") {
|
||||||
throw new Error(`Request failed: ${json["msg"]}`)
|
throw new Error(json["msg"])
|
||||||
}
|
}
|
||||||
|
|
||||||
return json["data"]
|
return json["data"]
|
||||||
}
|
}, [server])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextServer.Provider value={{server, setServer, fetchData}}>
|
<ContextServer.Provider value={{server, setServer, fetchData}}>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useContext } from "react"
|
import React, { useCallback, useContext } from "react"
|
||||||
import useLocalStorageState from "../hooks/useLocalStorageState"
|
import useLocalStorageState from "../hooks/useLocalStorageState"
|
||||||
import ContextServer from "../contexts/ContextServer"
|
import ContextServer from "../contexts/ContextServer"
|
||||||
import ContextUser from "../contexts/ContextUser"
|
import ContextUser from "../contexts/ContextUser"
|
||||||
|
@ -26,7 +26,7 @@ export default function GlobalUser({ children }) {
|
||||||
* @param init - Additional arguments to pass to the `init` parameter of {@link fetch}.
|
* @param init - Additional arguments to pass to the `init` parameter of {@link fetch}.
|
||||||
* @returns {Promise<*>}
|
* @returns {Promise<*>}
|
||||||
*/
|
*/
|
||||||
const fetchDataAuth = async (method, path, body, init) => {
|
const fetchDataAuth = useCallback(async (method, path, body, init) => {
|
||||||
if(!user) {
|
if(!user) {
|
||||||
throw new Error("Not logged in")
|
throw new Error("Not logged in")
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@ export default function GlobalUser({ children }) {
|
||||||
}
|
}
|
||||||
init["headers"]["Authorization"] = `Bearer ${user["token"]}`
|
init["headers"]["Authorization"] = `Bearer ${user["token"]}`
|
||||||
|
|
||||||
return await fetchData(path, init)
|
return await fetchData(method, path, body, init)
|
||||||
}
|
}, [fetchData, user])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to login to the active server with the passed credentials.
|
* Try to login to the active server with the passed credentials.
|
||||||
|
@ -49,7 +49,7 @@ export default function GlobalUser({ children }) {
|
||||||
* @param password - The user's password.
|
* @param password - The user's password.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const login = async (email, password) => {
|
const login = useCallback(async (email, password) => {
|
||||||
console.debug("Contacting server to login...")
|
console.debug("Contacting server to login...")
|
||||||
const data = await fetchData("POST", `/api/login`, {
|
const data = await fetchData("POST", `/api/login`, {
|
||||||
"email": email,
|
"email": email,
|
||||||
|
@ -65,18 +65,18 @@ export default function GlobalUser({ children }) {
|
||||||
})
|
})
|
||||||
|
|
||||||
console.info("Login successful!")
|
console.info("Login successful!")
|
||||||
}
|
}, [fetchData, setUser])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout from the currently active server.
|
* Logout from the currently active server.
|
||||||
*/
|
*/
|
||||||
const logout = () => {
|
const logout = useCallback(() => {
|
||||||
console.debug("Clearing login state...")
|
console.debug("Clearing login state...")
|
||||||
setUser(null)
|
setUser(null)
|
||||||
console.debug("Cleared login state!")
|
console.debug("Cleared login state!")
|
||||||
|
|
||||||
console.info("Logout successful!")
|
console.info("Logout successful!")
|
||||||
}
|
}, [setUser])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextUser.Provider value={{user, login, logout, fetchDataAuth}}>
|
<ContextUser.Provider value={{user, login, logout, fetchDataAuth}}>
|
||||||
|
|
12
code/frontend/src/components/Loading.js
Normal file
12
code/frontend/src/components/Loading.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from "react"
|
||||||
|
import { faSpinner } from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
|
|
||||||
|
|
||||||
|
export default function Loading({ ...props }) {
|
||||||
|
return (
|
||||||
|
<div {...props}>
|
||||||
|
<FontAwesomeIcon icon={faSpinner} pulse={true}/> Loading...
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
17
code/frontend/src/hooks/useAsyncEffect.js
Normal file
17
code/frontend/src/hooks/useAsyncEffect.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link useEffect}, but with an async effect.
|
||||||
|
*
|
||||||
|
* @warning Breaks `react-hooks/exaustive-deps`.
|
||||||
|
*
|
||||||
|
* @param effect - The async effect.
|
||||||
|
* @param deps - The dependencies of the hook.
|
||||||
|
*/
|
||||||
|
export default function useAsyncEffect(effect, deps) {
|
||||||
|
useEffect(() => {
|
||||||
|
effect()
|
||||||
|
}, [effect, ...deps])
|
||||||
|
}
|
69
code/frontend/src/hooks/useData.js
Normal file
69
code/frontend/src/hooks/useData.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { useCallback, useContext, useEffect, useState } from "react"
|
||||||
|
import ContextUser from "../contexts/ContextUser"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook which fetches data from the backend on the first render of a component.
|
||||||
|
*
|
||||||
|
* @param fetchData - The function to use when fetching data.
|
||||||
|
* @param method - The HTTP method to use.
|
||||||
|
* @param path - The HTTP path to fetch the data at.
|
||||||
|
* @param body - The body of the HTTP request (it will be JSONified before being sent).
|
||||||
|
* @param init - Additional `init` parameters to pass to `fetch`.
|
||||||
|
* @returns {{data: *, refresh: function, error: Error}}
|
||||||
|
*/
|
||||||
|
export default function useData(fetchData, method, path, body, init) {
|
||||||
|
const [error, setError] = useState(null)
|
||||||
|
const [data, setData] = useState(null)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const started = (loading || data || error)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load data from the API.
|
||||||
|
*/
|
||||||
|
const load = useCallback(
|
||||||
|
async () => {
|
||||||
|
console.debug(`Trying to ${method} ${path}...`)
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const _data = await fetchData(method, path, body, init)
|
||||||
|
setData(_data)
|
||||||
|
} catch(e) {
|
||||||
|
setError(e)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[fetchData, method, path, body, init, setData, setError, setLoading]
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the data loaded from the API and try to load it again.
|
||||||
|
*/
|
||||||
|
const refresh = useCallback(
|
||||||
|
async () => {
|
||||||
|
console.debug("Clearing data...")
|
||||||
|
setData(null)
|
||||||
|
|
||||||
|
console.debug("Clearing error...")
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
await load()
|
||||||
|
},
|
||||||
|
[load, setData, setError]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if(!started) {
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[load, started]
|
||||||
|
)
|
||||||
|
|
||||||
|
return {data, error, loading, started, refresh}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useState } from "react"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,17 +7,21 @@ import { useEffect, useState } from "react"
|
||||||
export default function useLocalStorageState(key, def) {
|
export default function useLocalStorageState(key, def) {
|
||||||
const [value, setValue] = useState(null);
|
const [value, setValue] = useState(null);
|
||||||
|
|
||||||
const load = () => {
|
/**
|
||||||
|
* Load the `key` from the {@link localStorage} into `value`, defaulting to `def` if it is not found.
|
||||||
|
*/
|
||||||
|
const load = useCallback(
|
||||||
|
() => {
|
||||||
if(localStorage) {
|
if(localStorage) {
|
||||||
console.debug(`Loading ${key} from localStorage...`)
|
console.debug(`Loading ${key} from localStorage...`)
|
||||||
let value = JSON.parse(localStorage.getItem(key))
|
let _value = JSON.parse(localStorage.getItem(key))
|
||||||
|
|
||||||
if(value) {
|
if(_value) {
|
||||||
console.debug(`Loaded ${key} from localStorage!`)
|
console.info(`Loaded ${key} from localStorage!`)
|
||||||
return value
|
return _value
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.debug(`There is no value ${key} stored, defaulting...`)
|
console.info(`There is no value ${key} stored, defaulting...`)
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,28 +29,49 @@ export default function useLocalStorageState(key, def) {
|
||||||
console.warn(`Can't load value as localStorage doesn't seem to be available, defaulting...`)
|
console.warn(`Can't load value as localStorage doesn't seem to be available, defaulting...`)
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[key, def]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
/**
|
||||||
if(!value) {
|
* Save a value to the {@link localStorage}.
|
||||||
setValue(load())
|
*/
|
||||||
}
|
const save = useCallback(
|
||||||
}, [value])
|
(value) => {
|
||||||
|
|
||||||
const save = (value) => {
|
|
||||||
if(localStorage) {
|
if(localStorage) {
|
||||||
console.debug(`Saving ${key} to localStorage...`)
|
console.debug(`Saving ${key} to localStorage...`)
|
||||||
localStorage.setItem(key, JSON.stringify(value))
|
localStorage.setItem(key, JSON.stringify(value))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.warn(`Can't save theme; localStorage doesn't seem to be available...`)
|
console.warn(`Can't save ${key}; localStorage doesn't seem to be available...`)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[key]
|
||||||
|
)
|
||||||
|
|
||||||
const setAndSave = (value) => {
|
/**
|
||||||
|
* Set `value` and save it to the {@link localStorage}.
|
||||||
|
*/
|
||||||
|
const setAndSave = useCallback(
|
||||||
|
(value) => {
|
||||||
setValue(value)
|
setValue(value)
|
||||||
save(value)
|
save(value)
|
||||||
|
},
|
||||||
|
[setValue, save]
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When the component first renders, try to load the value from the localStorage.
|
||||||
|
*/
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if(!value) {
|
||||||
|
console.debug(`This is the first render, loading ${key} from the localStorage...`)
|
||||||
|
setValue(load())
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[value, setValue, load, key],
|
||||||
|
)
|
||||||
|
|
||||||
return [value, setAndSave]
|
return [value, setAndSave]
|
||||||
}
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
import React, { useContext, useState } from "react"
|
import React from "react"
|
||||||
import Style from "./PageLogin.module.css"
|
import Style from "./PageLogin.module.css"
|
||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
import BoxFull from "../components/BoxFull"
|
|
||||||
import ContextUser from "../contexts/ContextUser"
|
|
||||||
import { useHistory } from "react-router"
|
|
||||||
import BoxSetServer from "../components/BoxSetServer"
|
import BoxSetServer from "../components/BoxSetServer"
|
||||||
import BoxLogin from "../components/BoxLogin"
|
import BoxLogin from "../components/BoxLogin"
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import Style from "./PageRepositories.module.css"
|
import Style from "./PageRepositories.module.css"
|
||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
import BoxFull from "../components/BoxFull"
|
import BoxRepositoriesActive from "../components/BoxRepositoriesActive"
|
||||||
|
import BoxRepositoriesArchived from "../components/BoxRepositoriesArchived"
|
||||||
|
|
||||||
|
|
||||||
export default function PageRepositories({ children, className, ...props }) {
|
export default function PageRepositories({ children, className, ...props }) {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(Style.PageRepositories, className)} {...props}>
|
<div className={classNames(Style.PageRepositories, className)} {...props}>
|
||||||
<BoxFull header={"Your active repositories"} className={Style.ActiveRepositories}>
|
<BoxRepositoriesActive/>
|
||||||
🚧 Not implemented.
|
<BoxRepositoriesArchived/>
|
||||||
</BoxFull>
|
|
||||||
<BoxFull header={"Your archived repositories"} className={Style.ArchivedRepositories}>
|
|
||||||
🚧 Not implemented.
|
|
||||||
</BoxFull>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue