diff --git a/src/allStyles.styl b/src/allStyles.styl index b7c74d6b0874104c1d6a320082091a8771693b27..f9179349afccad7659f45fc2aa4e10e8c4185d3e 100644 --- a/src/allStyles.styl +++ b/src/allStyles.styl @@ -9,6 +9,8 @@ //webpack import 'react-notifications/lib/notifications.css'; @import "../customChangedLib/react-notifications/notifications.styl" +@import "../src/components/404/style.styl" + @import "../src/components/helpers/styles.styl" @import "../src/components/loggerPanel/styles.styl" diff --git a/src/app.tsx b/src/app.tsx index 2557a1966c83b922264562c0af55ed43a28e9816..618acbd928a9ec99c62bcb13156640c9faa559e0 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -50,22 +50,31 @@ import ManageTagsSite from './components/sites/manageTagsSite/manageTagsSite' import ManageTagsHeaderBarContent from './components/sites/manageTagsSite/headerBarContent' import OpenExercisesSite from './components/sites/allAccessibleExercisesSite/openExercises/allOpenExercisesSite' -import OpenExercisesSiteHeaderBarContent from './components/sites/allAccessibleExercisesSite/openExercises/headerBarContent' +import OpenExercisesSiteHeaderBarContent + from './components/sites/allAccessibleExercisesSite/openExercises/headerBarContent' -import ClosedExercisesSite from './components/sites/allAccessibleExercisesSite/closedExercisesSite/allClosedExercisesSite' -import ClosedExercisesSiteHeaderBarContent from './components/sites/allAccessibleExercisesSite/closedExercisesSite/headerBarContent' +import ClosedExercisesSite + from './components/sites/allAccessibleExercisesSite/closedExercisesSite/allClosedExercisesSite' +import ClosedExercisesSiteHeaderBarContent + from './components/sites/allAccessibleExercisesSite/closedExercisesSite/headerBarContent' import ReleaseSite from './components/sites/manageOwnOrGroupExerciseComponents/releasesSite/releasesSite' -import ReleaseSiteHeaderBarContent from './components/sites/manageOwnOrGroupExerciseComponents/releasesSite/headerBarContent' +import ReleaseSiteHeaderBarContent + from './components/sites/manageOwnOrGroupExerciseComponents/releasesSite/headerBarContent' import SubmissionsSite from './components/sites/manageOwnOrGroupExerciseComponents/submissionsSite/submissionsSite' -import SubmissionsSiteHeaderBarContent from './components/sites/manageOwnOrGroupExerciseComponents/submissionsSite/headerBarContent' +import SubmissionsSiteHeaderBarContent + from './components/sites/manageOwnOrGroupExerciseComponents/submissionsSite/headerBarContent' -import OpenExercisesViaCodeSite from './components/sites/allAccessibleExercisesSite/openExercises/openExercisesViaCodeSite' -import OpenExercisesViaVisibilitySite from './components/sites/allAccessibleExercisesSite/openExercises/openExercisesViaVisibilitySite' +import OpenExercisesViaCodeSite + from './components/sites/allAccessibleExercisesSite/openExercises/openExercisesViaCodeSite' +import OpenExercisesViaVisibilitySite + from './components/sites/allAccessibleExercisesSite/openExercises/openExercisesViaVisibilitySite' -import ClosedExercisesSiteViaCode from './components/sites/allAccessibleExercisesSite/closedExercisesSite/closedExercisesSiteViaCode' -import ClosedExercisesSiteViaVisibility from './components/sites/allAccessibleExercisesSite/closedExercisesSite/closedExercisesSiteViaVisibility' +import ClosedExercisesSiteViaCode + from './components/sites/allAccessibleExercisesSite/closedExercisesSite/closedExercisesSiteViaCode' +import ClosedExercisesSiteViaVisibility + from './components/sites/allAccessibleExercisesSite/closedExercisesSite/closedExercisesSiteViaVisibility' import ExerciseEditorSite from './components/sites/exerciseEditorSite/exerciseEditorSite' import ExerciseEditorSiteHeaderBarContent from './components/sites/exerciseEditorSite/headerBarContent' @@ -105,6 +114,7 @@ import Spinner from './components/helpers/spinner' import SystemSettingsSite from './components/sites/systemSettingsSite/systemSettingsSite' import {tempPrintDivId} from './constants' +import Chat404 from '../src/components/404/Chat404' const mapStateToProps = (rootState: RootState) => { return { @@ -117,8 +127,8 @@ const mapStateToProps = (rootState: RootState) => { } const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({ - setUsername, -}, dispatch) + setUsername, + }, dispatch) const stateProps = returntypeof(mapStateToProps); @@ -144,436 +154,450 @@ class App extends React.Component<Props, any> { <div - className={this.props.isSiteHeaderBarDisplayed && !this.props.isSiteHeaderBarCollapsed ? 'fh site-content' : 'fh site-content-no-site-header-bar'}> + className={this.props.isSiteHeaderBarDisplayed && !this.props.isSiteHeaderBarCollapsed + ? 'fh site-content' + : 'fh site-content-no-site-header-bar'}> <div className="fh relative-positioned"> { //TODO i think this is not needed anymore because the login overlay handles this already?? this.props.userId === userDataInitial.userData.id && - <LoginForm /> + <LoginForm/> } - { - //syntax guides - } - <Route exact path={constants.markdownSiteExerciseSyntaxGuide} render={(props: RouteComponentProps<any>) => - <PageContentWrapper> - <ExerciseSyntaxSite /> - </PageContentWrapper> - }/> - - <Route exact path={constants.markdownSiteTestSyntaxGuide} render={(props: RouteComponentProps<any>) => - <PageContentWrapper> - <TestSyntaxSite/> - </PageContentWrapper> - }/> + <Switch> + { + //syntax guides + } + <Route exact path={constants.markdownSiteExerciseSyntaxGuide} render={(props: RouteComponentProps<any>) => + <PageContentWrapper> + <ExerciseSyntaxSite/> + </PageContentWrapper> + }/> + <Route exact path={constants.markdownSiteTestSyntaxGuide} render={(props: RouteComponentProps<any>) => + <PageContentWrapper> + <TestSyntaxSite/> + </PageContentWrapper> + }/> - { - //start site - } - <Route exact path={constants.homeLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper> - <HomeSite/> - </PageContentWrapper> - }/> - { - //dashboard + { + //start site + } + <Route exact path={constants.homeLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper> + <HomeSite/> + </PageContentWrapper> + }/> - <Route exact path={constants.dashboardLinkPath} render={(props: RouteComponentProps<any>) => + { + //dashboard + + <Route exact path={constants.dashboardLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + //contains nothing yet + // headerBar={ + // <CommonHeaderBar> + // <DashboardSiteHeaderBarContent/> + // </CommonHeaderBar> + // } + > + <DashboardSite/> + </PageContentWrapper> + }/> + } + + { + //manage custom projects site + } + <Route exact path={constants.manageCustomProjectsSiteLinkMatchPath} + render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <ManageCustomProjectsSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <ManageCustomProjectsSite/> + </PageContentWrapper> + }/> + + { + //edit custom projects site (create, edit) + } + <Route exact path={constants.createCustomProjectSiteToLinkPath} + render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <EditCustomProjectSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <EditCustomProjectSite {...props}/> + </PageContentWrapper> + }/> + + <Route exact path={constants.getCustomProjectMatchLinkPath} render={(props: RouteComponentProps<any>) => <PageContentWrapper - //contains nothing yet - // headerBar={ - // <CommonHeaderBar> - // <DashboardSiteHeaderBarContent/> - // </CommonHeaderBar> - // } + headerBar={ + <CommonHeaderBar> + <EditCustomProjectSiteHeaderBarContent/> + </CommonHeaderBar> + } > - <DashboardSite/> + <EditCustomProjectSite {...props}/> </PageContentWrapper> }/> - } - { - //manage custom projects site - } - <Route exact path={constants.manageCustomProjectsSiteLinkMatchPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <ManageCustomProjectsSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <ManageCustomProjectsSite/> - </PageContentWrapper> - }/> - { - //edit custom projects site (create, edit) - } - <Route exact path={constants.createCustomProjectSiteToLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <EditCustomProjectSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <EditCustomProjectSite {...props}/> - </PageContentWrapper> - }/> - - <Route exact path={constants.getCustomProjectMatchLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <EditCustomProjectSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <EditCustomProjectSite {...props}/> - </PageContentWrapper> - }/> + { + //assessment statistics site + } + <Route exact path={constants.assessmentStatisticsSiteLinkMatchPath} + render={(props: RouteComponentProps<any>) => + <PageContentWrapper + > + <AssessmentStatisticsSite {...props}/> + </PageContentWrapper> + }/> - { - //assessment statistics site - } - <Route exact path={constants.assessmentStatisticsSiteLinkMatchPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - > - <AssessmentStatisticsSite {...props}/> - </PageContentWrapper> - }/> + { + //tutor view + } + <Route exact path={constants.tutorViewSiteLinkMatchPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <TutorViewSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <TutorViewSite {...props}/> + </PageContentWrapper> + }/> + { + //exercise editor create, update, copy + } + <Route exact path={constants.exerciseEditorCreateNewLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <ExerciseEditorSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <ExerciseEditorSite {...props}/> + </PageContentWrapper> + }/> - { - //tutor view - } - <Route exact path={constants.tutorViewSiteLinkMatchPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <TutorViewSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <TutorViewSite {...props}/> - </PageContentWrapper> - }/> + <Route exact path={constants.exerciseEditorChangeLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <ExerciseEditorSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <ExerciseEditorSite {...props}/> + </PageContentWrapper> + }/> - { - //exercise editor create, update, copy - } - <Route exact path={constants.exerciseEditorCreateNewLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <ExerciseEditorSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <ExerciseEditorSite {...props}/> - </PageContentWrapper> - }/> - - <Route exact path={constants.exerciseEditorChangeLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <ExerciseEditorSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <ExerciseEditorSite {...props}/> - </PageContentWrapper> - }/> + { + //do exercise site + } + <Route exact path={constants.doExerciseSiteLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <DoExerciseSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <DoExerciseSite {...props}/> + </PageContentWrapper> + }/> - { - //do exercise site - } - <Route exact path={constants.doExerciseSiteLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <DoExerciseSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <DoExerciseSite {...props}/> - </PageContentWrapper> - }/> + { + // --- manage exercises --- + } + <Route exact path={constants.ownExercisesLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <OwnExercisesHeaderBarContent/> + </CommonHeaderBar> + } + > + <OwnExercisesSite/> + </PageContentWrapper> + }/> - { - // --- manage exercises --- - } - <Route exact path={constants.ownExercisesLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <OwnExercisesHeaderBarContent/> - </CommonHeaderBar> - } - > - <OwnExercisesSite/> - </PageContentWrapper> - }/> - - <Route exact path={constants.groupExercisesLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <GroupExercisesHeaderBarContent/> - </CommonHeaderBar> - } - > - <GroupExercisesSite/> - </PageContentWrapper> - }/> - - <Route exact path={constants.exerciseReleasesLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <ReleaseSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <ReleaseSite {...props} /> - </PageContentWrapper> - }/> - - <Route exact path={constants.submissionsSiteLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <SubmissionsSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <SubmissionsSite {...props}/> - </PageContentWrapper> - }/> + <Route exact path={constants.groupExercisesLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <GroupExercisesHeaderBarContent/> + </CommonHeaderBar> + } + > + <GroupExercisesSite/> + </PageContentWrapper> + }/> - { - //do exercises sites + <Route exact path={constants.exerciseReleasesLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <ReleaseSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <ReleaseSite {...props} /> + </PageContentWrapper> + }/> - } + <Route exact path={constants.submissionsSiteLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <SubmissionsSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <SubmissionsSite {...props}/> + </PageContentWrapper> + }/> - <Route exact path={constants.openExercisesLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <OpenExercisesSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <OpenExercisesSite/> - </PageContentWrapper> - }/> - - <Route exact path={constants.closedExercisesLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <ClosedExercisesSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <ClosedExercisesSite/> - </PageContentWrapper> - }/> + { + //do exercises sites - { - //open exercises sites - } - <Route exact path={constants.openExercisesViaCodeLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <OpenExercisesSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <OpenExercisesViaCodeSite shouldLoadData={true} canDisplayEnterReleaseCodeDialog={true} - canDisplayTagFilterPanel={true}/> - </PageContentWrapper> - }/> - - <Route exact path={constants.openExercisesViaVisibilityLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <OpenExercisesSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <OpenExercisesViaVisibilitySite shouldLoadData={true} canDisplayEnterReleaseCodeDialog={true} - canDisplayTagFilterPanel={true}/> - </PageContentWrapper> - }/> + } - { - //closed exercises sites - } - <Route exact path={constants.closedExercisesViaCodeLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <ClosedExercisesSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <ClosedExercisesSiteViaCode shouldLoadData={true} canDisplayTagFilterPanel={true}/> - </PageContentWrapper> - }/> - - <Route exact path={constants.closedExercisesViaVisibilityLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <ClosedExercisesSiteHeaderBarContent/> - </CommonHeaderBar> - } - > - <ClosedExercisesSiteViaVisibility shouldLoadData={true} canDisplayTagFilterPanel={true}/> - </PageContentWrapper> - }/> + <Route exact path={constants.openExercisesLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <OpenExercisesSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <OpenExercisesSite/> + </PageContentWrapper> + }/> - { - // --- groups --- - } + <Route exact path={constants.closedExercisesLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <ClosedExercisesSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <ClosedExercisesSite/> + </PageContentWrapper> + }/> - <Route exact path={constants.groupsLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <GroupsHeaderBarContent/> - </CommonHeaderBar> - } - > - <GroupsSite/> - </PageContentWrapper> - }/> - - <Route exact path={constants.singleGroupLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <SingleGroupHeaderBarContent/> - </CommonHeaderBar> - } - > - <SingleGroupSite {...props}/> - </PageContentWrapper> - }/> + { + //open exercises sites + } + <Route exact path={constants.openExercisesViaCodeLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <OpenExercisesSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <OpenExercisesViaCodeSite shouldLoadData={true} canDisplayEnterReleaseCodeDialog={true} + canDisplayTagFilterPanel={true}/> + </PageContentWrapper> + }/> - { - // --- roles --- - } + <Route exact path={constants.openExercisesViaVisibilityLinkPath} + render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <OpenExercisesSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <OpenExercisesViaVisibilitySite shouldLoadData={true} canDisplayEnterReleaseCodeDialog={true} + canDisplayTagFilterPanel={true}/> + </PageContentWrapper> + }/> + + { + //closed exercises sites + } + <Route exact path={constants.closedExercisesViaCodeLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <ClosedExercisesSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <ClosedExercisesSiteViaCode shouldLoadData={true} canDisplayTagFilterPanel={true}/> + </PageContentWrapper> + }/> - <Route exact path={constants.groupRolesLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <GroupRolesHeaderBarContent/> - </CommonHeaderBar> - } - > - <GroupRoleSite/> - </PageContentWrapper> - }/> - - <Route exact path={constants.systemRolesLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <SystemRolesHeaderBarContent/> - </CommonHeaderBar> - } - > - <SystemRolesSite/> - </PageContentWrapper> - }/> + <Route exact path={constants.closedExercisesViaVisibilityLinkPath} + render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <ClosedExercisesSiteHeaderBarContent/> + </CommonHeaderBar> + } + > + <ClosedExercisesSiteViaVisibility shouldLoadData={true} canDisplayTagFilterPanel={true}/> + </PageContentWrapper> + }/> + + { + // --- groups --- + } + + <Route exact path={constants.groupsLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <GroupsHeaderBarContent/> + </CommonHeaderBar> + } + > + <GroupsSite/> + </PageContentWrapper> + }/> - { - // --- user settings --- - } - <Route exact path={constants.ownSettingsLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - > - <SettingSite/> - </PageContentWrapper> - }/> + <Route exact path={constants.singleGroupLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <SingleGroupHeaderBarContent/> + </CommonHeaderBar> + } + > + <SingleGroupSite {...props}/> + </PageContentWrapper> + }/> - { - // --- new users --- - } - <Route path={constants.manageNewUsersLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <ManageNewUsersHeaderBarContent/> - </CommonHeaderBar> - } - > - <ManageNewUsersSite {...props}/> - </PageContentWrapper> - }/> + { + // --- roles --- + } - { - // --- activated users --- - } + <Route exact path={constants.groupRolesLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <GroupRolesHeaderBarContent/> + </CommonHeaderBar> + } + > + <GroupRoleSite/> + </PageContentWrapper> + }/> - <Route path={constants.manageActivatedUsersLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <ManageActivatedUsersHeaderBarContent/> - </CommonHeaderBar> - } - > - <ManageActivatedUsersSite {...props}/> - </PageContentWrapper> - }/> + <Route exact path={constants.systemRolesLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <SystemRolesHeaderBarContent/> + </CommonHeaderBar> + } + > + <SystemRolesSite/> + </PageContentWrapper> + }/> + { + // --- user settings --- + } + <Route exact path={constants.ownSettingsLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + > + <SettingSite/> + </PageContentWrapper> + }/> - { - // --- tags --- - } - <Route exact path={constants.manageTagsLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper - headerBar={ - <CommonHeaderBar> - <ManageTagsHeaderBarContent/> - </CommonHeaderBar> - } - > - <ManageTagsSite/> - </PageContentWrapper> - }/> + { + // --- new users --- + } + <Route path={constants.manageNewUsersLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <ManageNewUsersHeaderBarContent/> + </CommonHeaderBar> + } + > + <ManageNewUsersSite {...props}/> + </PageContentWrapper> + }/> - { - //--- system settings - } - <Route exact path={constants.systemSettingsSiteLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper> - <SystemSettingsSite/> - </PageContentWrapper> - }/> + { + // --- activated users --- + } - { - // --- about --- - } - <Route exact path={constants.aboutLinkPath} render={(props: RouteComponentProps<any>) => - <PageContentWrapper> - <AboutSite/> - </PageContentWrapper> - }/> + <Route path={constants.manageActivatedUsersLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <ManageActivatedUsersHeaderBarContent/> + </CommonHeaderBar> + } + > + <ManageActivatedUsersSite {...props}/> + </PageContentWrapper> + }/> + { + // --- tags --- + } + <Route exact path={constants.manageTagsLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper + headerBar={ + <CommonHeaderBar> + <ManageTagsHeaderBarContent/> + </CommonHeaderBar> + } + > + <ManageTagsSite/> + </PageContentWrapper> + }/> + + { + //--- system settings + } + <Route exact path={constants.systemSettingsSiteLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper> + <SystemSettingsSite/> + </PageContentWrapper> + }/> + + { + // --- about --- + } + <Route exact path={constants.aboutLinkPath} render={(props: RouteComponentProps<any>) => + <PageContentWrapper> + <AboutSite/> + </PageContentWrapper> + }/> + + <Route render={(props: RouteComponentProps<any>) => + <PageContentWrapper> + <Chat404/> + </PageContentWrapper> + } + /> + </Switch> <LoggerWrapper/> diff --git a/src/components/404/Chat404.tsx b/src/components/404/Chat404.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0e82fe311532fd18f38bc49fb868a1da19a4bd13 --- /dev/null +++ b/src/components/404/Chat404.tsx @@ -0,0 +1,460 @@ +import * as React from "react"; +import {connect} from "react-redux"; +import {bindActionCreators, Dispatch} from "redux"; +import {returntypeof} from 'react-redux-typescript'; +import {RootState} from '../../state/reducers' +import {notExhaustive} from '../../state/reducers/_notExhausiveHelper' +import {delay, wait} from '../../../debug' +import {Icon} from 'semantic-ui-react' + +export interface MyProps { + //readonly test: string +} + +const mapStateToProps = (rootState: RootState /*, props: MyProps*/) => { + return { + //test0: rootState... + //test: props.test + } +} + +const mapDispatchToProps = (dispatch: Dispatch<any>) => bindActionCreators({ + //imported reducer funcs here + + }, dispatch) + + +const stateProps = returntypeof(mapStateToProps); +const dispatchProps = returntypeof(mapDispatchToProps); +type Props = typeof stateProps & typeof dispatchProps; + + +interface ConversationUserEntered { + readonly kind: 'user-entered' + readonly message: string +} + +interface ConversationMessage { + readonly kind: 'message' + /** + * undefined for own user + */ + readonly name?: string + readonly message: string + readonly withLoading: boolean +} + + +type ChatMessage = ConversationUserEntered | ConversationMessage + + +interface State { + // tslint:disable-next-line + readonly currentConversation: ChatMessage[] +} + +const chatWrapperId = 'chat-window-wrapper' + +class Chat404 extends React.Component<Props, State> { + + /** + * after unmount cancel recursion + */ + disposed = false + + /** + * a deep copy because we need to modify loading state + */ + // tslint:disable-next-line + conversationCopy = JSON.parse(JSON.stringify(conversion)) as ChatMessage[] + + mainDiv: HTMLDivElement | null = null + + constructor(props: Props) { + super(props) + + this.state = { + currentConversation: [], + } + } + + componentDidMount(): void { + + this.addNextMessage(0) + } + + componentWillUnmount(): void { + this.disposed = true + } + + + getRndDelay(): number { + // tslint:disable-next-line + return this.rnd(1000, 2000) + } + + rnd(inclusiveMin: number, inclusiveMax: number): number { + return Math.floor(Math.random() * (inclusiveMax + 1 - inclusiveMin)) + inclusiveMin; + } + + async addNextMessage(index: number): Promise<void> { + + if (this.disposed) return + + if (index >= this.conversationCopy.length) { + return + } + + // tslint:disable-next-line + let msg = this.conversationCopy[index] + + if (msg.kind === 'message') { + + await wait(this.getRndDelay()) + + const wasLoading = msg.withLoading + + if (msg.withLoading) { + + + if (this.disposed) return + + //first add loading... + this.setState((prevState: State) => { + return { + currentConversation: [...prevState.currentConversation, msg], + } as State + }) + } + + await wait(this.getRndDelay()) + + if (wasLoading) { + + msg = { + ...msg, + withLoading: false + } as ChatMessage + + if (this.disposed) return + + this.setState((prevState: State) => { + return { + currentConversation: [...prevState.currentConversation.filter( + (value, index1) => index1 !== prevState.currentConversation.length - 1), msg], + } as State + }) + + } else { + + if (this.disposed) return + + this.setState((prevState: State) => { + return { + currentConversation: [...prevState.currentConversation, msg], + } as State + }) + + } + + + this.addNextMessage(index + 1) + + } else { + + await wait(this.getRndDelay()) + + if (this.disposed) return + + //set immediately + this.setState((prevState: State) => { + return { + currentConversation: [...prevState.currentConversation, msg], + } as State + }) + + this.addNextMessage(index + 1) + + } + + } + + render(): JSX.Element { + return ( + <div className="chat-component"> + <div className="watermark"> + 404 + </div> + + <div className="down-button clickable" onClick={() => { + + const scrollables = document.getElementById(chatWrapperId) + + if (scrollables && this.mainDiv) { + scrollables.scrollTo(0, this.mainDiv.scrollHeight) + } + }}> + <Icon name="chevron down"/> + </div> + + <div className="fh fw scrollable" id={chatWrapperId}> + <div ref={p => this.mainDiv = p} className="chat-window"> + { + this.state.currentConversation.map((value, index) => { + + const key = index + + switch (value.kind) { + case 'user-entered': + return (<div key={key} className="chat-entry user-entered">{value.message}</div>) + + case 'message': + + if (value.name === undefined) { + + if (value.withLoading) { + return (<div key={key} className="chat-entry own-message is-typing"> + <div></div> + <div> + <span></span> + <span></span> + <span></span> + </div> + </div>) + } + + return (<div key={key} className="chat-entry own-message">{value.message}</div>) + } + + if (value.withLoading) { + return (<div key={key} className="chat-entry foreign-message is-typing"> + <div className={`user-name${value.name === 'Janis' + ? ' alternate' + : ''}`}>{value.name}</div> + <div> + <span></span> + <span></span> + <span></span> + </div> + </div>) + } + + return (<div key={key} className="chat-entry foreign-message"> + <div className={`user-name${value.name === 'Janis' + ? ' alternate' + : ''}`}>{value.name}</div> + <div>{value.message}</div> + </div>) + + + default: + notExhaustive(value) + } + }) + } + </div> + </div> + </div> + ) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Chat404) + + +const conversion: ReadonlyArray<ChatMessage> = [ + { + kind: 'user-entered', + message: 'Steffen hat den Chat betreten' + }, + { + kind: 'user-entered', + message: 'Janis hat den Chat betreten' + }, + { + kind: 'message', + message: '???', + withLoading: false + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Hey Janis, ich glaube da hat mal wieder jemand mit der URL rumgespielt. Oder kennst du die geforderte Seite?' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Hm, komisch. Wie kommt man denn auf sowas?' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Vorallem, wer tippt heute noch manuell eine URL ein, oder haben wir irgendwo einen falschen Link gesetzt?' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Ausgeschlossen, bei YAPEX gibt es keine Fehler!!!1!!11!' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Jaja, von wegen keine Fehler. Ich habe dir aber doch gleich gesagt, dass wir einen vernünftigen 404-Seite brauchen!' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Also wir können doch nicht alles abfangen und eine leere Seite reicht vollkommen aus.' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Dann kann man aber nicht erkennen, dass die Seite tatsächlich nicht existiert. Wir brauchen schon irgendeine coole 404-Seite.' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Na da bin ich aber gespannt.' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Es könnten so Binärzahlen runter rieseln, die dann 404 ergeben.' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Ja klar, geht es noch aufwendiger?' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Statt Binärzahlen könnten wir auch Links zu unterschiedlichen Seiten von YAPEX runterfallen lassen, die nach Häufigkeit ihrer Verwendung unterschiedlich groß ausfallen, zuerst das YAPEX-Logo nachbilden und dann in 404 zerfallen.' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'OK, ich mache, sobald ich dazu komme, eine einfache klassische 404-Seite.' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Wir könnten auch 404 binär darstellen lassen, dann in unseren Editor anzeigen lassen, in welchem gerade ein Programm zur Umwandlung von Binärzahlen in Dezimalzahlen geschrieben wird und dann ein Testfall ausgeführt wird, der eben 404 liefert! Soll ich ein Ticket erstellen?' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Noch mehr Wünsche? Du und deine ganzen Tickets, die werden wir eh nie schaffen abzuarbeiten...' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Ich kann schließlich nichts dafür immer auf irgendwelche Fehler zu stoßen.' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Stimmt, das kann nur Talent sein.' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'War das jetzt ironisch? Also ein paar gute Ideen waren schon dabei und YAPEX ist doch gut gewachsen. Gut, es gibt immer etwas zu tun, und ich hätte doch noch die ein oder andere Idee...' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: '...und ich darf die immer umsetzen. Wir bräuchten da dringend noch jemand, der mitmacht.' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Sag mal, ist besagte Person immer noch auf dieser nicht vorhandenen Seiten? Die hat demnach viel zu viel Zeit, vielleicht sollten wir sie mal anschreiben?!' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Weiß ich doch nicht. Bestimmt beim Bearbeiten von einer deiner Aufgabe eingeschlafen und mit dem Kopf auf die Tastatur gekommen...' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Du meinst ich habe zu leichte und langweilige Aufgaben gestellt? Ich habe auch das Gefühl, dass da noch eine gewisse Herausforderung fehlt.' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Ich sag dazu jetzt mal lieber nichts.' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Ach komm. Da fällt mir ein, ich könnte mal einen einigermaßen intelligenten Chatbot in Java programmieren lassen, nur müssten wir unsere Tests etwas anpassen.' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Das kann dann aber jemand anderes umsetzen.' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Das ist bestimmt total einfach.' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Natürlich... total einfach, weißt du was ich da noch alles ändern muss? Momentan geht das so sowieso nicht. Und wer will schon so eine Aufgabe bearbeiten?' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Hrm, zeitlich sieht es bei mir sowieso eng aus. Aber ich kann das mal im Hinterkopf behalten.' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Also machen wir jetzt einfach eine simple 404-Seite?' + }, + { + kind: 'message', + name: 'Steffen', + withLoading: true, + message: 'Gut, aber nur als Platzhalter, bis wir mal Zeit finden eine bessere Seite zu erstellen.' + }, + { + kind: 'message', + name: 'Janis', + withLoading: true, + message: 'Super, find ich gut.' + } +] + diff --git a/src/components/404/style.styl b/src/components/404/style.styl new file mode 100644 index 0000000000000000000000000000000000000000..43067b454104a8ab76d3ab4bf76dced8b0fe0178 --- /dev/null +++ b/src/components/404/style.styl @@ -0,0 +1,126 @@ + +.chat-component { + height 100% + width 100% + position: relative + overflow hidden + + .down-button { + width 40px + height 40px + border-radius 50% + background-color white + color #099eff + position absolute + right 20px + bottom 20px + display flex + justify-content: center; + align-items: center; + z-index 2 + + .icon { + margin 0 + } + } + + .watermark { + position absolute + left calc(50% - 500px) + top 100px + height 450px + font-size 600px + //color: rgba(214, 214, 214, 0.66) + //text-shadow: 0 0 20px rgb(214, 214, 214) + padding-top: 200px; + + //from https://designshack.net/articles/css/12-fun-css-text-shadows-you-can-copy-and-paste/ + background-color: #666666; + -webkit-background-clip: text; + -moz-background-clip: text; + background-clip: text; + color: transparent; + text-shadow: rgba(255, 255, 255, 0.5) 0px 3px 3px; + } +} + +.chat-window-wrapper { + width 100% + height 100% + overflow auto +} + +.chat-window { + width 100% + display flex + flex-direction column + padding 1em + position relative +} + +.chat-entry { + + display flex + flex-direction column + border-radius 5px + margin-bottom 1em + max-width 90% + padding 0.3em + + &.user-entered { + padding: 0.3em 7px; + background-color rgb(219, 232, 246) + align-self center + } + + &.foreign-message { + background-color white + align-self: flex-start + + .user-name { + color: #ff603e + + &.alternate { + color #2b8aff + } + + } + } + + + &.own-message { + background-color rgb(232, 253, 203) + align-self flex-end + } + + //see https://codepen.io/fusco/pen/XbpaYv + + &.is-typing { + > div:nth-child(2) { + span { + width 10px + height 10px + background-color gray + border-radius 50% + display inline-block + margin 0 3px + opacity 0.4 + + for $i in (1..3) { + &:nth-of-type({$i}) { + animation 1s chat-typing-blink infinite ($i * .3333s) + } + } + + } + } + } + +} + +//see https://codepen.io/fusco/pen/XbpaYv +@keyframes chat-typing-blink { + 50% { + opacity: 1; + } +}