1
Fork 0
mirror of https://github.com/pds-nest/nest.git synced 2024-11-22 04:54:18 +00:00

🔧 Refactor more page stuff

This commit is contained in:
Steffo 2021-05-25 04:06:14 +02:00
parent db167a7d8a
commit 6c0248cda6
Signed by: steffo
GPG key ID: 6965406171929D01
29 changed files with 485 additions and 341 deletions

View file

@ -141,7 +141,8 @@ export default {
errorViewNotAllowed: "Errore: Non è permesso effettuare la richiesta.", errorViewNotAllowed: "Errore: Non è permesso effettuare la richiesta.",
errorServerNotConfigured: "Errore: Non è stato configurato nessun server.", errorServerNotConfigured: "Errore: Non è stato configurato nessun server.",
errorDecodeError: "Errore: Non è stato possibile deserializzare i dati ricevuti dal backend.", errorDecodeError: "Errore: Non è stato possibile deserializzare i dati ricevuti dal backend.",
errorSerializationError: "Errore: Non è stato possibile serializzare i dati da inviare al backend." errorSerializationError: "Errore: Non è stato possibile serializzare i dati da inviare al backend.",
errorPageNotFound: "Errore: Pagina non trovata.", // TODO: Tradurre
}, },
// 🇬🇧 // 🇬🇧
en: { en: {

View file

@ -9,9 +9,15 @@ import PageRepositoryEdit from "./routes/PageRepositoryEdit"
import PageUsers from "./routes/PageUsers" import PageUsers from "./routes/PageUsers"
import PageRepositoryAnalyze from "./routes/PageRepositoryAnalyze" import PageRepositoryAnalyze from "./routes/PageRepositoryAnalyze"
import PageShare from "./routes/PageShare" import PageShare from "./routes/PageShare"
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"
import makeIcon from "./utils/makeIcon"
import useStrings from "./hooks/useStrings"
import Alert from "./components/base/Alert"
export default function PageSwitcher({ ...props }) { export default function PageSwitcher({ ...props }) {
const strings = useStrings()
return ( return (
<Switch {...props}> <Switch {...props}>
<Route path={"/repositories/create"} exact={true}> <Route path={"/repositories/create"} exact={true}>
@ -38,9 +44,12 @@ export default function PageSwitcher({ ...props }) {
<Route path={"/settings"} exact={true}> <Route path={"/settings"} exact={true}>
<PageSettings/> <PageSettings/>
</Route> </Route>
<Route path={"/"}> <Route path={"/"} exact={true}>
<PageLogin/> <PageLogin/>
</Route> </Route>
<Route>
<Alert color={"Red"}>{makeIcon(faQuestionCircle)} {strings.errorPageNotFound}</Alert>
</Route>
</Switch> </Switch>
) )
} }

View file

@ -0,0 +1,19 @@
import React from "react"
import Style from "./ButtonHeader.module.css"
import classNames from "classnames"
import Button from "./Button"
/**
* A {@link Button} without `boxShadow` and with `flexGrow`, to be used in {@link PageWithHeader}.
*
* @param className - Additional class(es) to add to the button.
* @param props - Additional props to pass to the button.
* @returns {JSX.Element}
* @constructor
*/
export default function ButtonHeader({ className, ...props }) {
return (
<Button className={classNames(Style.ButtonHeader, className)} {...props}/>
)
}

View file

@ -0,0 +1,4 @@
.ButtonHeader {
box-shadow: none;
flex-grow: 1;
}

View file

@ -0,0 +1,12 @@
import React from "react"
import Style from "./BodyFlex.module.css"
import classNames from "classnames"
export default function BodyFlex({ children, className, ...props }) {
return (
<div className={classNames(Style.BodyFlex, className)} {...props}>
{children}
</div>
)
}

View file

@ -1,9 +1,6 @@
.PageLogin { .BodyFlex {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch;
gap: 10px; gap: 10px;
width: 100%;
height: 100%;
} }

View file

@ -0,0 +1,17 @@
import React from "react"
import Style from "./BodyHorizontalHalves.module.css"
import classNames from "classnames"
export default function BodyHorizontalHalves({ upper, lower, className, ...props }) {
return (
<div className={classNames(Style.BodyHorizontalHalves, className)} {...props}>
<div className={Style.Upper}>
{upper}
</div>
<div className={Style.Lower}>
{lower}
</div>
</div>
)
}

View file

@ -0,0 +1,24 @@
.BodyHorizontalHalves {
display: grid;
grid-template-areas: "upper" "lower";
grid-template-rows: 1fr 1fr;
grid-gap: 10px;
}
.Upper {
grid-area: upper;
}
.Upper > * {
width: 100%;
height: 100%;
}
.Lower {
grid-area: lower;
}
.Lower > * {
width: 100%;
height: 100%;
}

View file

@ -0,0 +1,20 @@
import React from "react"
import Style from "./BodyHorizontalUpperGrow.module.css"
import classNames from "classnames"
export default function BodyHorizontalUpperGrow({ upper, lower, error, className, ...props }) {
return (
<div className={classNames(Style.BodyHorizontalUpperGrow, className)} {...props}>
<div className={Style.Upper}>
{upper}
</div>
<div className={Style.Lower}>
{lower}
</div>
<div className={Style.Error}>
{error}
</div>
</div>
)
}

View file

@ -0,0 +1,29 @@
.BodyHorizontalUpperGrow {
display: grid;
grid-template-areas: "upper" "lower" "error";
grid-template-rows: 1fr auto auto;
}
.Upper {
grid-area: upper;
}
.Upper > * {
width: 100%;
height: 100%;
}
.Lower {
grid-area: lower;
margin-top: 10px;
}
.Lower > * {
width: 100%;
height: 100%;
}
.Error {
grid-area: error;
margin-top: 10px;
}

View file

@ -0,0 +1,22 @@
import React from "react"
import Style from "./PageWithHeader.module.css"
import classNames from "classnames"
export default function PageWithHeader({ header, buttons, children, className, ...props }) {
return (
<div className={classNames(Style.PageWithHeader, className)} {...props}>
<div className={Style.Header}>
{header}
</div>
{buttons ?
<div className={Style.Buttons}>
{buttons}
</div>
: null}
<div className={Style.Body}>
{children}
</div>
</div>
)
}

View file

@ -0,0 +1,38 @@
.PageWithHeader {
width: 100%;
height: 100%;
display: grid;
grid-template-areas:
"header button"
"body body";
grid-template-columns: 1fr auto;
grid-template-rows: auto 1fr;
}
.Header {
grid-area: header;
margin-bottom: 10px;
}
.Buttons {
grid-area: button;
margin-left: 10px;
margin-bottom: 10px;
display: flex;
flex-direction: row;
}
.Buttons > * {
flex-grow: 1;
}
.Body {
grid-area: body;
}
.Body > * {
width: 100%;
height: 100%;
}

View file

@ -1,6 +1,5 @@
import React, { useContext, useMemo, useState } from "react" import React, { useContext, useMemo, useState } from "react"
import Style from "./RepositoryViewer.module.css" import Style from "./RepositoryViewer.module.css"
import classNames from "classnames"
import ContextLanguage from "../../contexts/ContextLanguage" import ContextLanguage from "../../contexts/ContextLanguage"
import useBackendResource from "../../hooks/useBackendResource" import useBackendResource from "../../hooks/useBackendResource"
import useBackendViewset from "../../hooks/useBackendViewset" import useBackendViewset from "../../hooks/useBackendViewset"
@ -9,7 +8,7 @@ import countTweetWords from "../../utils/countTweetWords"
import BoxHeader from "../base/BoxHeader" import BoxHeader from "../base/BoxHeader"
import Loading from "../base/Loading" import Loading from "../base/Loading"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faFolder, faFolderOpen, faTrash } from "@fortawesome/free-solid-svg-icons" import { faFolder, faFolderOpen, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"
import BoxRepositoryTweets from "../interactive/BoxRepositoryTweets" import BoxRepositoryTweets from "../interactive/BoxRepositoryTweets"
import PickerVisualization from "../interactive/PickerVisualization" import PickerVisualization from "../interactive/PickerVisualization"
import BoxVisualizationWordcloud from "../interactive/BoxVisualizationWordcloud" import BoxVisualizationWordcloud from "../interactive/BoxVisualizationWordcloud"
@ -29,6 +28,9 @@ import BoxFilterDatetime from "../interactive/BoxFilterDatetime"
import BoxFilterHasPlace from "../interactive/BoxFilterHasPlace" import BoxFilterHasPlace from "../interactive/BoxFilterHasPlace"
import BoxFilterHasImage from "../interactive/BoxFilterHasImage" import BoxFilterHasImage from "../interactive/BoxFilterHasImage"
import BoxFilterIsNotRetweet from "../interactive/BoxFilterIsNotRetweet" import BoxFilterIsNotRetweet from "../interactive/BoxFilterIsNotRetweet"
import PageWithHeader from "../base/layout/PageWithHeader"
import ButtonHeader from "../base/ButtonHeader"
import AlertError from "../interactive/AlertError"
export default function RepositoryViewer({ id, className, ...props }) { export default function RepositoryViewer({ id, className, ...props }) {
@ -49,7 +51,11 @@ export default function RepositoryViewer({ id, className, ...props }) {
const mapViewHook = useMapAreaState() const mapViewHook = useMapAreaState()
// Repository // Repository
const repositoryBr = useBackendResource( const {
resource: repository,
error: repositoryError,
firstLoad: repositoryFirstLoad,
} = useBackendResource(
`/api/v1/repositories/${id}`, `/api/v1/repositories/${id}`,
{ {
retrieve: true, retrieve: true,
@ -58,11 +64,13 @@ export default function RepositoryViewer({ id, className, ...props }) {
action: false, action: false,
}, },
) )
const repository = repositoryBr.error ? null : repositoryBr.resource
// Tweets // Tweets
const rawTweetsBv = useBackendViewset( const {
resources: tweets,
error: tweetsError,
firstLoad: tweetsFirstLoad,
} = useBackendViewset(
`/api/v1/repositories/${id}/tweets/`, `/api/v1/repositories/${id}/tweets/`,
"snowflake", "snowflake",
{ {
@ -75,44 +83,50 @@ export default function RepositoryViewer({ id, className, ...props }) {
action: false, action: false,
}, },
) )
const rawTweets = rawTweetsBv.resources && rawTweetsBv.error ? [] : rawTweetsBv.resources
// Filtering // Filtering
let tweets = rawTweets const filteredTweets = useMemo(
() => {
let tempTweets = tweets
for(const filter of filters) { for(const filter of filters) {
tweets = tweets.filter(tweet => filter.exec(tweet)) tempTweets = tempTweets.filter(tweet => filter.exec(tweet))
} }
return tempTweets
},
[tweets, filters]
)
// Words // Words
const words = useMemo( const words = useMemo(
() => objectToWordcloudFormat(countTweetWords(tweets)), () => objectToWordcloudFormat(countTweetWords(filteredTweets)),
[tweets], [filteredTweets],
) )
// Page header
const header = useMemo(
() => (
<BoxHeader>
<FontAwesomeIcon icon={repository?.is_active ? faFolderOpen : faFolder}/> {repository?.name}
</BoxHeader>
),
[repository?.is_active, repository?.name]
)
let contents // Page contents
if(!repositoryBr.firstLoad || !rawTweetsBv.firstLoad) { const contents = useMemo(
contents = <> () => {
<BoxHeader className={Style.Header}> if(!repositoryFirstLoad || !tweetsFirstLoad) {
<Loading/> return <Loading/>
</BoxHeader>
</>
} }
else if(repository === null) { else if(repositoryError) {
contents = <> return <AlertError error={repositoryError}/>
<BoxHeader className={Style.Header}> }
<FontAwesomeIcon icon={faTrash}/> <i>{strings.repoDeleted}</i> else if(tweetsError) {
</BoxHeader> return <AlertError error={tweetsError}/>
</>
} }
else { else {
contents = <> return (
<BoxHeader className={Style.Header}> <div className={Style.RepositoryViewer}>
<FontAwesomeIcon icon={repository.is_active ? faFolderOpen : faFolder}/> {repository.name}
</BoxHeader>
<BoxRepositoryTweets className={Style.Tweets}/> <BoxRepositoryTweets className={Style.Tweets}/>
<PickerVisualization className={Style.VisualizationPicker}/> <PickerVisualization className={Style.VisualizationPicker}/>
{visualizationTab === "wordcloud" ? <BoxVisualizationWordcloud className={Style.Visualization}/> : null} {visualizationTab === "wordcloud" ? <BoxVisualizationWordcloud className={Style.Visualization}/> : null}
@ -130,8 +144,12 @@ export default function RepositoryViewer({ id, className, ...props }) {
{filterTab === "time" ? <BoxFilterDatetime className={Style.AddFilter}/> : null} {filterTab === "time" ? <BoxFilterDatetime className={Style.AddFilter}/> : null}
{filterTab === "place" ? <BoxFilterHasPlace className={Style.AddFilter}/> : null} {filterTab === "place" ? <BoxFilterHasPlace className={Style.AddFilter}/> : null}
{filterTab === "location" ? <BoxFilterLocation className={Style.AddFilter}/> : null} {filterTab === "location" ? <BoxFilterLocation className={Style.AddFilter}/> : null}
</> </div>
)
} }
},
[repositoryFirstLoad, tweetsFirstLoad, visualizationTab, filterTab]
)
return ( return (
<ContextRepositoryViewer.Provider <ContextRepositoryViewer.Provider
@ -145,20 +163,16 @@ export default function RepositoryViewer({ id, className, ...props }) {
appendFilter, appendFilter,
spliceFilter, spliceFilter,
removeFilter, removeFilter,
repositoryBr,
repository, repository,
rawTweetsBv, rawTweets: tweets,
rawTweets, tweets: filteredTweets,
tweets,
words, words,
mapViewHook, mapViewHook,
}} }}
> >
<PageWithHeader header={header}>
<div className={classNames(Style.RepositoryViewer, className)} {...props}>
{contents} {contents}
</div> </PageWithHeader>
</ContextRepositoryViewer.Provider> </ContextRepositoryViewer.Provider>
) )
} }

View file

@ -2,7 +2,6 @@
display: grid; display: grid;
grid-template-areas: grid-template-areas:
"h h"
"a b" "a b"
"a c" "a c"
"d e" "d e"
@ -10,16 +9,12 @@
grid-gap: 10px; grid-gap: 10px;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-template-rows: auto auto 1fr auto auto; grid-template-rows: auto 1fr auto auto;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.Header {
grid-area: h;
}
.Tweets { .Tweets {
grid-area: a; grid-area: a;
} }

View file

@ -1,6 +1,4 @@
import React, { useContext } from "react" import React, { useContext } from "react"
import Style from "./PageLogin.module.css"
import classNames from "classnames"
import BoxSetServer from "../components/interactive/BoxSetServer" import BoxSetServer from "../components/interactive/BoxSetServer"
import BoxLogin from "../components/interactive/BoxLogin" import BoxLogin from "../components/interactive/BoxLogin"
import ContextUser from "../contexts/ContextUser" import ContextUser from "../contexts/ContextUser"
@ -9,9 +7,11 @@ import BoxHeader from "../components/base/BoxHeader"
import useStrings from "../hooks/useStrings" import useStrings from "../hooks/useStrings"
import BoxFull from "../components/base/BoxFull" import BoxFull from "../components/base/BoxFull"
import SelectLanguage from "../components/interactive/SelectLanguage" import SelectLanguage from "../components/interactive/SelectLanguage"
import PageWithHeader from "../components/base/layout/PageWithHeader"
import BodyFlex from "../components/base/layout/BodyFlex"
export default function PageLogin({ className, ...props }) { export default function PageLogin({ ...props }) {
const {user} = useContext(ContextUser) const {user} = useContext(ContextUser)
const strings = useStrings() const strings = useStrings()
@ -20,15 +20,21 @@ export default function PageLogin({ className, ...props }) {
} }
return ( return (
<div className={classNames(Style.PageLogin, className)} {...props}> <PageWithHeader
header={
<BoxHeader> <BoxHeader>
{strings.welcomeToNest} {strings.welcomeToNest}
</BoxHeader> </BoxHeader>
}
{...props}
>
<BodyFlex>
<BoxSetServer/> <BoxSetServer/>
<BoxLogin/> <BoxLogin/>
<BoxFull header={strings.changeLang}> <BoxFull header={strings.changeLang}>
<SelectLanguage/> <SelectLanguage/>
</BoxFull> </BoxFull>
</div> </BodyFlex>
</PageWithHeader>
) )
} }

View file

@ -1,6 +1,4 @@
import React, { useCallback, useContext } from "react" import React, { useCallback, useContext } from "react"
import Style from "./PageRepositoriesList.module.css"
import classNames from "classnames"
import useBackendViewset from "../hooks/useBackendViewset" import useBackendViewset from "../hooks/useBackendViewset"
import BoxRepositories from "../components/interactive/BoxRepositories" import BoxRepositories from "../components/interactive/BoxRepositories"
import { useHistory } from "react-router" import { useHistory } from "react-router"
@ -8,7 +6,9 @@ import ContextLanguage from "../contexts/ContextLanguage"
import BoxHeader from "../components/base/BoxHeader" import BoxHeader from "../components/base/BoxHeader"
import { faHome, faPlus } from "@fortawesome/free-solid-svg-icons" import { faHome, faPlus } from "@fortawesome/free-solid-svg-icons"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import Button from "../components/base/Button" import PageWithHeader from "../components/base/layout/PageWithHeader"
import ButtonHeader from "../components/base/ButtonHeader"
import BodyHorizontalHalves from "../components/base/layout/BodyHorizontalHalves"
export default function PageRepositoriesList({ children, className, ...props }) { export default function PageRepositoriesList({ children, className, ...props }) {
@ -27,21 +27,25 @@ export default function PageRepositoriesList({ children, className, ...props })
) )
return ( return (
<div className={classNames(Style.PageRepositories, className)} {...props}> <PageWithHeader
<BoxHeader className={Style.Header}> header={
<BoxHeader>
<FontAwesomeIcon icon={faHome}/> {strings.dashboard} <FontAwesomeIcon icon={faHome}/> {strings.dashboard}
</BoxHeader> </BoxHeader>
<div className={Style.Buttons}> }
<Button buttons={
<ButtonHeader
icon={faPlus} icon={faPlus}
color={"Green"} color={"Green"}
onClick={() => history.push("/repositories/create")} onClick={() => history.push("/repositories/create")}
> >
{strings.createRepo} {strings.createRepo}
</Button> </ButtonHeader>
</div> }
>
<BodyHorizontalHalves
upper={
<BoxRepositories <BoxRepositories
className={Style.ActiveRepositories}
header={strings.menuActive} header={strings.menuActive}
loading={!bv.firstLoad} loading={!bv.firstLoad}
running={bv.running} running={bv.running}
@ -52,8 +56,9 @@ export default function PageRepositoriesList({ children, className, ...props })
archive={archive} archive={archive}
edit={pk => history.push(`/repositories/${pk}/edit`)} edit={pk => history.push(`/repositories/${pk}/edit`)}
/> />
}
lower={
<BoxRepositories <BoxRepositories
className={Style.ArchivedRepositories}
header={strings.menuArchived} header={strings.menuArchived}
loading={!bv.firstLoad} loading={!bv.firstLoad}
running={bv.running} running={bv.running}
@ -61,6 +66,8 @@ export default function PageRepositoriesList({ children, className, ...props })
view={pk => history.push(`/repositories/${pk}`)} view={pk => history.push(`/repositories/${pk}`)}
destroy={bv.destroyResource} destroy={bv.destroyResource}
/> />
</div> }
/>
</PageWithHeader>
) )
} }

View file

@ -1,39 +0,0 @@
.PageRepositories {
display: grid;
grid-template-areas:
"h x"
"a a"
"b b";
grid-template-columns: 4fr 1fr;
grid-template-rows: auto 1fr 1fr;
grid-gap: 10px;
width: 100%;
height: 100%;
}
.Header {
grid-area: h;
}
.Buttons {
grid-area: x;
display: flex;
flex-direction: row;
align-items: stretch;
}
.Buttons > * {
box-shadow: none;
flex-grow: 1;
}
.ActiveRepositories {
grid-area: a;
}
.ArchivedRepositories {
grid-area: b;
}

View file

@ -1,27 +1,40 @@
import React, { useContext } from "react" import React, { useContext } from "react"
import Style from "./PageRepositoryAlerts.module.css" import Style from "./PageRepositoryAlerts.module.css"
import classNames from "classnames"
import BoxFull from "../components/base/BoxFull" import BoxFull from "../components/base/BoxFull"
import ContextLanguage from "../contexts/ContextLanguage" import ContextLanguage from "../contexts/ContextLanguage"
import BoxHeader from "../components/base/BoxHeader" import BoxHeader from "../components/base/BoxHeader"
import { useParams } from "react-router" import { useHistory, useParams } from "react-router"
import { faBell, faPlus } from "@fortawesome/free-solid-svg-icons"
import PageWithHeader from "../components/base/layout/PageWithHeader"
import ButtonHeader from "../components/base/ButtonHeader"
import makeIcon from "../utils/makeIcon"
export default function PageRepositoryAlerts({ children, className, ...props }) { export default function PageRepositoryAlerts({ children, className, ...props }) {
const { strings } = useContext(ContextLanguage) const { strings } = useContext(ContextLanguage)
const { id } = useParams() const { id } = useParams()
const history = useHistory()
return ( return (
<div className={classNames(Style.PageAlerts, className)} {...props}> <PageWithHeader
header={
<BoxHeader className={Style.Header}> <BoxHeader className={Style.Header}>
{strings.alerts} {makeIcon(faBell)} {strings.alerts}
</BoxHeader> </BoxHeader>
}
buttons={
<ButtonHeader
icon={faPlus}
color={"Green"}
onClick={() => history.push(`/repositories/${id}/alerts/create`)}
>
{strings.alertCreate}
</ButtonHeader>
}
>
<BoxFull header={strings.alertTitle} className={Style.YourAlerts}> <BoxFull header={strings.alertTitle} className={Style.YourAlerts}>
{strings.notImplemented} {strings.notImplemented}
</BoxFull> </BoxFull>
<BoxFull header={strings.alertCreate} className={Style.CreateAlert}> </PageWithHeader>
{strings.notImplemented}
</BoxFull>
</div>
) )
} }

View file

@ -2,10 +2,10 @@
display: grid; display: grid;
grid-template-areas: grid-template-areas:
"a" "a x"
"b" "b b";
"c"; grid-template-columns: 4fr 1fr;
grid-template-rows: auto 1fr 1fr; grid-template-rows: auto 1fr;
grid-gap: 10px; grid-gap: 10px;
@ -17,10 +17,19 @@
grid-area: a; grid-area: a;
} }
.Buttons {
grid-area: x;
display: flex;
flex-direction: row;
align-items: stretch;
}
.Buttons > * {
box-shadow: none;
flex-grow: 1;
}
.YourAlerts { .YourAlerts {
grid-area: b; grid-area: b;
} }
.CreateAlert {
grid-area: c;
}

View file

@ -1,20 +1,25 @@
import React, { useContext } from "react" import React, { useContext } from "react"
import Style from "./PageRepositoryCreate.module.css"
import classNames from "classnames"
import BoxHeader from "../components/base/BoxHeader" import BoxHeader from "../components/base/BoxHeader"
import RepositoryEditor from "../components/providers/RepositoryEditor" import RepositoryEditor from "../components/providers/RepositoryEditor"
import ContextLanguage from "../contexts/ContextLanguage" import ContextLanguage from "../contexts/ContextLanguage"
import PageWithHeader from "../components/base/layout/PageWithHeader"
import makeIcon from "../utils/makeIcon"
import { faPlus } from "@fortawesome/free-solid-svg-icons"
export default function PageRepositoryCreate({ children, className, ...props }) { export default function PageRepositoryCreate({ ...props }) {
const { strings } = useContext(ContextLanguage) const { strings } = useContext(ContextLanguage)
return ( return (
<div className={classNames(Style.PageHome, className)} {...props}> <PageWithHeader
<BoxHeader className={Style.Header}> header={
{strings.dashboardTitle} <BoxHeader>
{makeIcon(faPlus)} {strings.dashboardTitle}
</BoxHeader> </BoxHeader>
<RepositoryEditor className={Style.RepositoryEditor}/> }
</div> {...props}
>
<RepositoryEditor/>
</PageWithHeader>
) )
} }

View file

@ -1,21 +0,0 @@
.PageHome {
display: grid;
grid-template-areas:
"a"
"b";
grid-template-rows: auto 1fr;
grid-gap: 10px;
width: 100%;
height: 100%;
}
.Header {
grid-area: a;
}
.RepositoryEditor {
grid-area: b;
}

View file

@ -1,6 +1,4 @@
import React, { useContext } from "react" import React, { useContext } from "react"
import Style from "./PageRepositoryCreate.module.css"
import classNames from "classnames"
import BoxHeader from "../components/base/BoxHeader" import BoxHeader from "../components/base/BoxHeader"
import RepositoryEditor from "../components/providers/RepositoryEditor" import RepositoryEditor from "../components/providers/RepositoryEditor"
import useBackendImmediately from "../hooks/useBackendImmediately" import useBackendImmediately from "../hooks/useBackendImmediately"
@ -8,6 +6,9 @@ import ContextUser from "../contexts/ContextUser"
import renderContents from "../utils/renderContents" import renderContents from "../utils/renderContents"
import { useParams } from "react-router" import { useParams } from "react-router"
import ContextLanguage from "../contexts/ContextLanguage" import ContextLanguage from "../contexts/ContextLanguage"
import PageWithHeader from "../components/base/layout/PageWithHeader"
import makeIcon from "../utils/makeIcon"
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons"
export default function PageRepositoryEdit({ className, ...props }) { export default function PageRepositoryEdit({ className, ...props }) {
@ -20,16 +21,20 @@ export default function PageRepositoryEdit({ className, ...props }) {
repositoryRequest, repositoryRequest,
data => { data => {
console.debug("Data: ", data) console.debug("Data: ", data)
return <RepositoryEditor className={Style.RepositoryEditor} {...data}/> return <RepositoryEditor {...data}/>
}, },
) )
return ( return (
<div className={classNames(Style.PageHome, className)} {...props}> <PageWithHeader
<BoxHeader className={Style.Header}> header={
{strings.repoEdit} <BoxHeader>
{makeIcon(faPencilAlt)} {strings.repoEdit}
</BoxHeader> </BoxHeader>
}
{...props}
>
{contents} {contents}
</div> </PageWithHeader>
) )
} }

View file

@ -1,18 +1,28 @@
import React, { useContext } from "react" import React, { useContext } from "react"
import Style from "./PageSettings.module.css"
import classNames from "classnames"
import BoxFull from "../components/base/BoxFull" import BoxFull from "../components/base/BoxFull"
import SelectTheme from "../components/interactive/SelectTheme" import SelectTheme from "../components/interactive/SelectTheme"
import BoxLoggedIn from "../components/interactive/BoxLoggedIn" import BoxLoggedIn from "../components/interactive/BoxLoggedIn"
import SelectLanguage from "../components/interactive/SelectLanguage" import SelectLanguage from "../components/interactive/SelectLanguage"
import ContextLanguage from "../contexts/ContextLanguage" import ContextLanguage from "../contexts/ContextLanguage"
import PageWithHeader from "../components/base/layout/PageWithHeader"
import BoxHeader from "../components/base/BoxHeader"
import { faCog } from "@fortawesome/free-solid-svg-icons"
import makeIcon from "../utils/makeIcon"
import BodyFlex from "../components/base/layout/BodyFlex"
export default function PageSettings({ children, className, ...props }) { export default function PageSettings({ children, className, ...props }) {
const { strings } = useContext(ContextLanguage) const { strings } = useContext(ContextLanguage)
return ( return (
<div className={classNames(Style.PageSettings, className)} {...props}> <PageWithHeader
header={
<BoxHeader>
{makeIcon(faCog)} {strings.settings}
</BoxHeader>
}
>
<BodyFlex>
<BoxLoggedIn/> <BoxLoggedIn/>
<BoxFull header={strings.changeLang}> <BoxFull header={strings.changeLang}>
<SelectLanguage/> <SelectLanguage/>
@ -20,6 +30,7 @@ export default function PageSettings({ children, className, ...props }) {
<BoxFull header={strings.switchTheme}> <BoxFull header={strings.switchTheme}>
<SelectTheme/> <SelectTheme/>
</BoxFull> </BoxFull>
</div> </BodyFlex>
</PageWithHeader>
) )
} }

View file

@ -1,9 +0,0 @@
.PageSettings {
display: flex;
flex-direction: column;
grid-gap: 10px;
width: 100%;
height: 100%;
}

View file

@ -1,14 +1,13 @@
import React, { useCallback, useContext } from "react" import React, { useCallback, useContext } from "react"
import classNames from "classnames"
import BoxHeader from "../components/base/BoxHeader" import BoxHeader from "../components/base/BoxHeader"
import ContextLanguage from "../contexts/ContextLanguage"
import Style from "./PageShare.module.css"
import BoxUserList from "../components/interactive/BoxUserList" import BoxUserList from "../components/interactive/BoxUserList"
import useBackendViewset from "../hooks/useBackendViewset" import useBackendViewset from "../hooks/useBackendViewset"
import { useParams } from "react-router" import { useParams } from "react-router"
import ContextUser from "../contexts/ContextUser" import ContextUser from "../contexts/ContextUser"
import Alert from "../components/base/Alert"
import useStrings from "../hooks/useStrings" import useStrings from "../hooks/useStrings"
import PageWithHeader from "../components/base/layout/PageWithHeader"
import BodyHorizontalHalves from "../components/base/layout/BodyHorizontalHalves"
import AlertError from "../components/interactive/AlertError"
export default function PageShare({ className, ...props }) { export default function PageShare({ className, ...props }) {
@ -71,30 +70,35 @@ export default function PageShare({ className, ...props }) {
) )
return ( return (
<div className={classNames(Style.PageShare, className)} {...props}> <PageWithHeader
<BoxHeader className={Style.Header}> header={
<BoxHeader>
{strings.repoShare} {strings.repoShare}
</BoxHeader> </BoxHeader>
}
>
<BodyHorizontalHalves
upper={
<BoxUserList <BoxUserList
className={Style.UserList}
users={users.filter(user => user["email"] !== loggedUser["email"] && !authorizations.map(a => a.email).includes(user.email))} users={users.filter(user => user["email"] !== loggedUser["email"] && !authorizations.map(a => a.email).includes(user.email))}
shareWithUser={shareWith} shareWithUser={shareWith}
header={strings.availableUsers} header={strings.availableUsers}
running={usersBvRunning && authBvRunning} running={usersBvRunning || authBvRunning}
/> />
}
lower={<>
<BoxUserList <BoxUserList
className={Style.SharingWith}
users={users.filter(user => user["email"] === loggedUser["email"] || authorizations.map(a => a.email).includes(user.email))} users={users.filter(user => user["email"] === loggedUser["email"] || authorizations.map(a => a.email).includes(user.email))}
unshareWithUser={unshareWith} unshareWithUser={unshareWith}
header={strings.sharingWith} header={strings.sharingWith}
running={usersBvRunning && authBvRunning} running={usersBvRunning || authBvRunning}
/> />
{authBvError ? </>}
<Alert color={"Red"} className={Style.Error}>{strings[authBvError?.data?.code ?? "errorUnknownError"]}</Alert> error={<>
: null} {authBvError ? <AlertError error={authBvError}/> : null}
{usersBvError ? {usersBvError ? <AlertError error={usersBvError}/> : null}
<Alert color={"Red"} className={Style.Error}>{strings[usersBvError?.data?.code ?? "errorUnknownError"]}</Alert> </>}
: null} />
</div> </PageWithHeader>
) )
} }

View file

@ -1,32 +0,0 @@
.PageShare {
display: grid;
grid-template-areas:
"a"
"b"
"c"
"d";
grid-template-rows: auto 1fr 1fr auto;
grid-template-columns: 1fr;
grid-gap: 10px;
width: 100%;
height: 100%;
}
.Header {
grid-area: a;
}
.UserList {
grid-area: b;
}
.SharingWith {
grid-area: c;
}
.Error {
grid-area: d;
}

View file

@ -1,29 +1,45 @@
import React, { useContext } from "react" import React, { useContext } from "react"
import Style from "./PageUsers.module.css"
import classNames from "classnames"
import BoxHeader from "../components/base/BoxHeader" import BoxHeader from "../components/base/BoxHeader"
import BoxUserCreate from "../components/interactive/BoxUserCreate" import BoxUserCreate from "../components/interactive/BoxUserCreate"
import useBackendViewset from "../hooks/useBackendViewset" import useBackendViewset from "../hooks/useBackendViewset"
import BoxUserList from "../components/interactive/BoxUserList" import BoxUserList from "../components/interactive/BoxUserList"
import ContextLanguage from "../contexts/ContextLanguage" import ContextLanguage from "../contexts/ContextLanguage"
import Alert from "../components/base/Alert" import PageWithHeader from "../components/base/layout/PageWithHeader"
import { faUserCog } from "@fortawesome/free-solid-svg-icons"
import makeIcon from "../utils/makeIcon"
import AlertError from "../components/interactive/AlertError"
import BodyHorizontalUpperGrow from "../components/base/layout/BodyHorizontalUpperGrow"
export default function PageUsers({ children, className, ...props }) { export default function PageUsers({ children, className, ...props }) {
const { strings } = useContext(ContextLanguage) const { strings } = useContext(ContextLanguage)
const bv = useBackendViewset("/api/v1/users/", "email") const {createResource, running, resources, destroyResource, error} = useBackendViewset("/api/v1/users/", "email")
return ( return (
<div className={classNames(Style.PageUsers, className)} {...props}> <PageWithHeader
<BoxHeader className={Style.Header}> header={
{strings.manageUsers} <BoxHeader>
{makeIcon(faUserCog)} {strings.manageUsers}
</BoxHeader> </BoxHeader>
<BoxUserCreate className={Style.CreateUser} createUser={bv.createResource} running={bv.running}/> }
<BoxUserList className={Style.UserList} users={bv.resources} destroyUser={bv.destroyResource} running={bv.running}/> >
{bv.error ? <BodyHorizontalUpperGrow
<Alert className={Style.Error} color={"red"}>{strings[bv.error?.data?.code ?? "errorUnknownError"]}</Alert> upper={
: null} <BoxUserList
</div> users={resources}
destroyUser={destroyResource}
running={running}
/>
}
lower={
<BoxUserCreate
createUser={createResource}
running={running}
/>
}
error={error ? <AlertError error={error}/> : null}
/>
</PageWithHeader>
) )
} }

View file

@ -1,32 +0,0 @@
.PageUsers {
display: grid;
grid-template-areas:
"a"
"b"
"c"
"e";
grid-template-rows: auto 1fr auto auto;
grid-template-columns: 1fr;
grid-gap: 10px;
width: 100%;
height: 100%;
}
.Header {
grid-area: a;
}
.UserList {
grid-area: b;
}
.CreateUser {
grid-area: c;
}
.Error {
grid-area: e;
}

View file

@ -4,7 +4,7 @@ import sw from "stopword"
const stopwords = [...sw.it, ...sw.en, "rt"] const stopwords = [...sw.it, ...sw.en, "rt"]
export default function countTweetWords(tweets = {}) { export default function countTweetWords(tweets = []) {
let words = {} let words = {}
for(const tweet of tweets) { for(const tweet of tweets) {
if(!tweet.content) { if(!tweet.content) {