",
11 | "license": "Apache-2.0"
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Banner.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import CourseSettings from "../../course-settings"
4 |
5 | import BannerImage from "../../banner.svg"
6 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
7 |
8 | const BannerWrapper = styled.header`
9 | height: 30rem;
10 | max-height: 50vh;
11 | display: flex;
12 | flex-direction: column;
13 | justify-content: center;
14 | align-items: center;
15 | margin-bottom: 2rem;
16 | background-color: #c0392b;
17 | background-image: url(${BannerImage});
18 | text-align: center;
19 | h1 {
20 | }
21 | h2 {
22 | }
23 | `
24 |
25 | const Heading = styled.div`
26 | font-family: "Roboto Slab", -apple-system, BlinkMacSystemFont, "Segoe UI",
27 | Roboto, "Helvetica Neue", Arial, Noto Sans, sans-serif, "Apple Color Emoji",
28 | "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
29 | font-weight: 500;
30 | font-size: 2rem;
31 | color: #c0392b;
32 | background: white;
33 | padding: 0.5rem;
34 | margin: 1rem;
35 | text-align: center;
36 | @media only screen and (min-width: 720px) {
37 | font-size: 3rem;
38 | }
39 | `
40 |
41 | const SubHeading = styled.div`
42 | font-family: 'Roboto Slab', -apple-system, BlinkMacSystemFont, 'Segoe UI',
43 | Roboto, 'Helvetica Neue', Arial, Noto Sans, sans-serif, 'Apple Color Emoji',
44 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
45 | font-weight: 500;
46 | color: #c0392b;
47 | background: white;
48 | padding .5rem;
49 | font-size: 1.4rem;
50 | margin: 0 1rem;
51 | `
52 |
53 | const Banner = () => (
54 |
55 | {CourseSettings.name}
56 | {CourseSettings.subtitle}
57 |
58 | )
59 |
60 | export default withSimpleErrorBoundary(Banner)
61 |
--------------------------------------------------------------------------------
/src/components/Button.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "gatsby"
3 | import styled from "styled-components"
4 | import { Button as MaterialButton } from "@material-ui/core"
5 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
6 |
7 | const StyledLink = styled(Link)`
8 | margin: 1rem 0.5rem;
9 | `
10 |
11 | const Button = ({ children, to, onClick, disabled, variant = "outlined" }) => (
12 |
17 | {children}
18 |
19 | )
20 |
21 | export default withSimpleErrorBoundary(Button)
22 |
--------------------------------------------------------------------------------
/src/components/Container.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
3 |
4 | const Container = styled.div`
5 | width: 90%;
6 | max-width: 800px;
7 | margin: 0 auto;
8 | `
9 |
10 | export default withSimpleErrorBoundary(Container)
11 |
--------------------------------------------------------------------------------
/src/components/ContentArea.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
4 |
5 | const ContentAreaContainer = styled.main`
6 | padding: 0 0.5rem;
7 | background-color: #fcfcfc;
8 | margin-bottom: 5rem;
9 | min-height: 80vh;
10 | font-size: 1rem;
11 | ${(props) =>
12 | props.mobileMenuOpen &&
13 | `
14 | display: none;
15 | `}
16 | `
17 |
18 | class ContentArea extends React.Component {
19 | render() {
20 | return (
21 |
25 | {this.props.children}
26 |
27 | )
28 | }
29 | }
30 |
31 | export default withSimpleErrorBoundary(ContentArea)
32 |
--------------------------------------------------------------------------------
/src/components/CoursePageFooter.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import PagesInThisSection from "../partials/PagesInThisSection"
4 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
5 |
6 | const CoursePageFooterWrapper = styled.footer`
7 | background-color: white;
8 | color: black;
9 | padding: 3rem;
10 | `
11 |
12 | const CoursePageFooterContent = styled.div`
13 | display: flex;
14 | justify-content: center;
15 | max-width: 960px;
16 | margin: 0 auto;
17 | `
18 |
19 | class CoursePageFooter extends React.Component {
20 | render() {
21 | return (
22 |
23 |
24 |
27 |
28 |
29 | )
30 | }
31 | }
32 |
33 | export default withSimpleErrorBoundary(CoursePageFooter)
34 |
--------------------------------------------------------------------------------
/src/components/Loading.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react"
2 | import styled from "styled-components"
3 |
4 | import { CircularProgress } from "@material-ui/core"
5 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
6 |
7 | const LoadingWrapper = styled.div`
8 | padding: 5rem;
9 | display: flex;
10 | align-items: center;
11 | flex-direction: column;
12 | width: 100%;
13 | ${(props) =>
14 | props.heightHint &&
15 | `
16 | height: ${props.heightHint};
17 | `}
18 | `
19 |
20 | const Loading = ({ children, loading = true, heightHint = "500px" }) => {
21 | if (loading) {
22 | return (
23 |
24 |
25 |
26 | )
27 | }
28 |
29 | return {children}
30 | }
31 |
32 | export default withSimpleErrorBoundary(Loading)
33 |
--------------------------------------------------------------------------------
/src/components/LoginControls.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react"
2 | import Button from "./Button"
3 | import { signOut, getCachedUserDetails } from "../services/moocfi"
4 | import LoginStateContext, {
5 | withLoginStateContext,
6 | } from "../contexes/LoginStateContext"
7 | import { withTranslation } from "react-i18next"
8 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
9 | import { faUser as profileIcon } from "@fortawesome/free-solid-svg-icons"
10 | import styled from "styled-components"
11 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
12 |
13 | const StyledIcon = styled(FontAwesomeIcon)`
14 | margin-right: 0.5rem;
15 | `
16 |
17 | class LoginControls extends React.Component {
18 | static contextType = LoginStateContext
19 |
20 | doSignOut = (e) => {
21 | e.preventDefault()
22 | signOut()
23 | }
24 |
25 | async componentDidMount() {
26 | if (!this.context.loggedIn) {
27 | return
28 | }
29 | const details = await getCachedUserDetails()
30 | let name = `${details?.user_field?.first_name || ""} ${
31 | details?.user_field?.last_name || ""
32 | }`.trim()
33 | if (name === "") {
34 | name = details.email
35 | }
36 | this.setState({
37 | name,
38 | })
39 | }
40 |
41 | state = {
42 | name: "Loading...",
43 | }
44 |
45 | render() {
46 | return this.context.loggedIn ? (
47 |
48 |
52 |
55 |
56 | ) : (
57 |
58 |
59 |
60 |
61 | )
62 | }
63 | }
64 |
65 | export default withTranslation("common")(
66 | withSimpleErrorBoundary(withLoginStateContext(LoginControls)),
67 | )
68 |
--------------------------------------------------------------------------------
/src/components/Logo.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import logo from "../images/logo.png"
3 | import styled from "styled-components"
4 | import "typeface-open-sans-condensed"
5 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
6 |
7 | const LogoImg = styled.img`
8 | width: 3.4em;
9 | height: 3.4em;
10 | margin-right: 0.5em;
11 | margin-bottom: 0;
12 | `
13 |
14 | const LogoTypography = styled.div`
15 | flex: 1;
16 | font-family: "Open Sans Condensed", sans-serif !important;
17 | font-size: 1.75em !important;
18 | `
19 |
20 | const StyledLink = styled.a`
21 | text-decoration: none;
22 | color: black;
23 | display: flex;
24 | align-items: center;
25 | padding: 1em;
26 | padding-right: 1.7em;
27 | height: 64px;
28 | background-color: white;
29 |
30 | :hover {
31 | text-decoration: none;
32 | color: black;
33 | }
34 | `
35 |
36 | const Logo = () => (
37 |
38 |
39 |
40 | MOOC.fi
41 |
42 |
43 | )
44 |
45 | export default withSimpleErrorBoundary(Logo)
46 |
--------------------------------------------------------------------------------
/src/components/MailinglistForm.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import { withTranslation } from "react-i18next"
4 | import { TextField, Button } from "@material-ui/core"
5 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
6 |
7 | const Container = styled.div`
8 | padding: 3rem;
9 | `
10 |
11 | const FieldContainer = styled.div`
12 | width: 100%;
13 | display: flex;
14 | height: 2.5rem;
15 |
16 | button {
17 | background-color: #22a7f0;
18 | color: white;
19 | border: 0;
20 | padding: 0 1rem;
21 | font-weight: bold;
22 | transition: all 0.2s;
23 | height: 3.5rem !important;
24 | margin-left: 0.5rem;
25 | &:hover {
26 | cursor: pointer;
27 | background-color: #4183d7;
28 | }
29 | }
30 | `
31 |
32 | class MailingListForm extends React.Component {
33 | constructor(props) {
34 | super(props)
35 | this.form = React.createRef()
36 | }
37 | render() {
38 | return (
39 |
40 | {props.t("mailingListTitle")}
41 |
67 |
68 | )
69 | }
70 | }
71 |
72 | export default withTranslation("common")(
73 | withSimpleErrorBoundary(MailingListForm),
74 | )
75 |
--------------------------------------------------------------------------------
/src/components/PointsBalloon/CourseProgress.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import PartProgress from "./PartProgress"
3 |
4 | const splitCourses = false
5 | const CourseProgress = ({
6 | data,
7 | appliesForStudyRight,
8 | currentCourseVariant,
9 | }) => {
10 | return (
11 | data &&
12 | (currentCourseVariant === "ohja-dl" ||
13 | currentCourseVariant === "ohja-nodl" ? (
14 |
15 |
Ohjelmoinnin jatkokurssi
16 | {Object.entries(data).map(([name, data]) => {
17 | return (
18 |
23 | )
24 | })}
25 |
26 | ) : (
27 |
28 | {splitCourses &&
Ohjelmoinnin perusteet
}
29 | {Object.entries(data).map(([name, data]) => {
30 | if (name === "osa08" && splitCourses) {
31 | return (
32 |
33 |
Ohjelmoinnin jatkokurssi
34 |
39 |
40 | )
41 | } else {
42 | return (
43 |
48 | )
49 | }
50 | })}
51 |
52 | ))
53 | )
54 | }
55 |
56 | export default CourseProgress
57 |
--------------------------------------------------------------------------------
/src/components/PointsBalloon/CustomTooltip.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
3 | import styled from "styled-components"
4 | import { SMALL_MEDIUM_BREAKPOINT } from "../../util/constants"
5 | import { withTranslation } from "react-i18next"
6 |
7 | const StyledTooltip = styled.div`
8 | background: white;
9 | border-radius: 0.25rem;
10 | padding: 0.5rem;
11 | p {
12 | margin-bottom: 0 !important;
13 | font-size: 0.9rem;
14 | }
15 | @media only screen and (max-width: ${SMALL_MEDIUM_BREAKPOINT}) {
16 | p {
17 | font-size: 0.75rem;
18 | }
19 | }
20 | `
21 |
22 | const CustomTooltip = (props) => {
23 | if (!props.active) {
24 | return null
25 | }
26 |
27 | return (
28 |
29 |
30 | {props.t("gotPoints")}: {props.payload?.[0].payload.n_points}
31 |
32 |
33 | {props.t("maxPoints")}: {props.payload?.[0].payload.max_points}
34 |
35 |
36 | )
37 | }
38 |
39 | export default withTranslation("points-balloon")(
40 | withSimpleErrorBoundary(CustomTooltip),
41 | )
42 |
--------------------------------------------------------------------------------
/src/components/PointsBalloon/PointsBalloonBalloon.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
3 | import Fab from "@material-ui/core/Fab"
4 | import styled from "styled-components"
5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
6 | import { faChartLine as icon } from "@fortawesome/free-solid-svg-icons"
7 |
8 | const StyledIcon = styled(FontAwesomeIcon)`
9 | color: white;
10 | font-size: 1.5em !important;
11 | `
12 |
13 | const StyledFab = styled(Fab)`
14 | background-color: #00a5ff !important;
15 | `
16 |
17 | const PointsBalloonBalloon = ({ onClick }) => (
18 |
19 |
20 |
21 | )
22 |
23 | export default withSimpleErrorBoundary(PointsBalloonBalloon)
24 |
--------------------------------------------------------------------------------
/src/components/PointsBalloon/index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react"
2 | import LoginStateContext from "../../contexes/LoginStateContext"
3 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
4 | import styled from "styled-components"
5 |
6 | import PointsBalloonBalloon from "./PointsBalloonBalloon"
7 | import PointsBalloonContent from "./PointsBalloonContent"
8 |
9 | const PointsBalloonContainer = styled.div`
10 | position: fixed;
11 | right: 1.5rem;
12 | bottom: 1.5rem;
13 | z-index: 50;
14 | `
15 |
16 | class PointsBalloon extends React.Component {
17 | static contextType = LoginStateContext
18 |
19 | state = {
20 | render: false,
21 | open: false,
22 | }
23 |
24 | componentDidMount() {
25 | this.setState({ render: true })
26 | }
27 |
28 | onClick = () => {
29 | this.setState({ open: true })
30 | }
31 |
32 | onClose = () => {
33 | this.setState({ open: false })
34 | }
35 |
36 | render() {
37 | if (!this.state.render || !this.context.loggedIn) {
38 | return
39 | }
40 | return (
41 |
42 | {!this.state.open && }
43 | {this.state.open && (
44 |
48 | )}
49 |
50 | )
51 | }
52 | }
53 |
54 | export default withSimpleErrorBoundary(PointsBalloon)
55 |
--------------------------------------------------------------------------------
/src/components/StyledDivider.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import { Divider } from "@material-ui/core"
3 |
4 | const StyledDivider = styled(Divider)`
5 | margin: 1rem 16px !important;
6 | `
7 |
8 | export default StyledDivider
9 |
--------------------------------------------------------------------------------
/src/components/TopBar.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import LoginControls from "./LoginControls"
4 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
5 | import LoginStateContext, {
6 | withLoginStateContext,
7 | } from "../contexes/LoginStateContext"
8 | import Button from "./Button"
9 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
10 | import { faChartLine as pointsIcon } from "@fortawesome/free-solid-svg-icons"
11 | import CourseSettings from "../../course-settings"
12 |
13 | const TopBarContainer = styled.div`
14 | height: 4rem;
15 | width: 100%;
16 | display: flex;
17 | justify-content: flex-end;
18 | @media only screen and (max-width: 1200px) {
19 | justify-content: center;
20 | }
21 | `
22 |
23 | const StyledIcon = styled(FontAwesomeIcon)`
24 | margin-right: 0.5rem;
25 | `
26 |
27 | class TopBar extends React.Component {
28 | static contextType = LoginStateContext
29 |
30 | render() {
31 | return (
32 |
33 | {CourseSettings.useNewPointsVisualization && this.context.loggedIn && (
34 |
38 | )}
39 |
40 |
41 | )
42 | }
43 | }
44 |
45 | export default withSimpleErrorBoundary(withLoginStateContext(TopBar))
46 |
--------------------------------------------------------------------------------
/src/components/TreeView/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import TreeViewItem from "./TreeViewItem"
3 | import styled from "styled-components"
4 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
5 |
6 | const StyledUl = styled.ul`
7 | margin-left: 0;
8 | margin-top: 0.5em;
9 | padding-left: 0;
10 | `
11 |
12 | class TreeView extends React.Component {
13 | render() {
14 | return (
15 |
16 | {this.props.data.map((top) => (
17 |
18 | ))}
19 |
20 | )
21 | }
22 | }
23 |
24 | export default withSimpleErrorBoundary(TreeView)
25 |
--------------------------------------------------------------------------------
/src/components/user/ConfirmEmail.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "gatsby"
3 | import { withTranslation } from "react-i18next"
4 | import EmailExample from "../../images/email-example.png"
5 |
6 | import styled from "styled-components"
7 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
8 |
9 | const InfoBox = styled.div`
10 | margin-bottom: 2rem;
11 | `
12 |
13 | const FormContainer = styled.div`
14 | height: 100%;
15 | margin-top: 2rem;
16 | `
17 |
18 | const StyledImage = styled.img`
19 | width: 100%;
20 | padding: 1rem 0;
21 | `
22 |
23 | class ConfirmEmail extends React.Component {
24 | onClick = async (e) => {
25 | e.preventDefault()
26 | }
27 |
28 | state = {
29 | email: undefined,
30 | password: undefined,
31 | submitting: false,
32 | error: false,
33 | }
34 |
35 | render() {
36 | return (
37 |
38 | {this.props.t("welcomeToCourse")}
39 |
40 | {this.props.t("emailSent")}
41 |
42 | {this.props.t("emailExample")}
43 |
44 |
48 |
49 |
50 | {this.props.t("nowContinue")}{" "}
51 | {this.props.t("toMaterial")}.
52 |
53 |
54 | )
55 | }
56 | }
57 |
58 | export default withTranslation("user")(withSimpleErrorBoundary(ConfirmEmail))
59 |
--------------------------------------------------------------------------------
/src/contexes/AbGroupContext.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export default React.createContext()
4 |
--------------------------------------------------------------------------------
/src/contexes/LoginStateContext.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { loggedIn, onLoginStateChanged } from "../services/moocfi"
3 |
4 | const LoginStateContext = React.createContext()
5 |
6 | export class LoginStateContextProvider extends React.Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | loggedIn: loggedIn(),
11 | }
12 | }
13 |
14 | componentDidMount() {
15 | onLoginStateChanged((loggedIn) => {
16 | this.setState({ loggedIn })
17 | })
18 | setTimeout(() => {
19 | this.setState({
20 | loggedIn: loggedIn(),
21 | })
22 | }, 5000)
23 | }
24 |
25 | render() {
26 | return (
27 |
28 | {this.props.children}
29 |
30 | )
31 | }
32 | }
33 |
34 | export function withLoginStateContext(Component) {
35 | return (props) => (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | export default LoginStateContext
43 |
--------------------------------------------------------------------------------
/src/contexes/PagesContext.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export default React.createContext()
4 |
--------------------------------------------------------------------------------
/src/html.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import PropTypes from "prop-types"
3 |
4 | export default class HTML extends React.Component {
5 | render() {
6 | return (
7 |
8 |
9 |
10 |
11 |
15 | {this.props.headComponents}
16 |
17 |
18 | {this.props.preBodyComponents}
19 |
24 | {this.props.postBodyComponents}
25 |
26 |
27 | )
28 | }
29 | }
30 |
31 | HTML.propTypes = {
32 | htmlAttributes: PropTypes.object,
33 | headComponents: PropTypes.array,
34 | bodyAttributes: PropTypes.object,
35 | preBodyComponents: PropTypes.array,
36 | body: PropTypes.string,
37 | postBodyComponents: PropTypes.array,
38 | }
39 |
--------------------------------------------------------------------------------
/src/i18n.js:
--------------------------------------------------------------------------------
1 | import i18n from "i18next"
2 | import { initReactI18next } from "react-i18next"
3 | import CourseSettings from "../course-settings"
4 | import commonEN from "./locales/common/en"
5 | import pointsBalloonEN from "./locales/pointsBalloon/en"
6 | import userEN from "./locales/user/en"
7 | import commonFI from "./locales/common/fi"
8 | import pointsBalloonFI from "./locales/pointsBalloon/fi"
9 | import userFI from "./locales/user/fi"
10 |
11 | const resources = {
12 | en: {
13 | common: commonEN,
14 | "points-balloon": pointsBalloonEN,
15 | user: userEN,
16 | },
17 | fi: {
18 | common: commonFI,
19 | "points-balloon": pointsBalloonFI,
20 | user: userFI,
21 | },
22 | }
23 |
24 | i18n.use(initReactI18next).init({
25 | resources,
26 | ns: ["common", "user", "points-balloon"],
27 | defaultNS: "common",
28 | react: {
29 | wait: true,
30 | },
31 | lng: CourseSettings.language,
32 | })
33 |
34 | export default i18n
35 |
--------------------------------------------------------------------------------
/src/images/email-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rage/programming-22/dffe6da3749231671f1cfa41185228cd1dcce6cf/src/images/email-example.png
--------------------------------------------------------------------------------
/src/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rage/programming-22/dffe6da3749231671f1cfa41185228cd1dcce6cf/src/images/logo.png
--------------------------------------------------------------------------------
/src/images/moocfi-logo-bw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rage/programming-22/dffe6da3749231671f1cfa41185228cd1dcce6cf/src/images/moocfi-logo-bw.png
--------------------------------------------------------------------------------
/src/images/uh-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rage/programming-22/dffe6da3749231671f1cfa41185228cd1dcce6cf/src/images/uh-logo.png
--------------------------------------------------------------------------------
/src/locales/pointsBalloon/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "gotPoints": "Awarded points: ",
3 | "maxPoints": "Maximum points: ",
4 | "programmingService": "Programming exercises",
5 | "quizService": "Quizzes",
6 | "totalPoints": "Total points",
7 | "noTimeLimit": "You are doing the course without deadlines, and cannot apply for a study place.",
8 | "canApplyForStudyRight": "To be applicable for a study place, 90% of the points from programming exercises of the time limited course are required. Your progress so far: ",
9 | "progress":"Progress (beta)",
10 | "close": "close ",
11 | "error":"Fetching progress data failed with the following error: "
12 | }
13 |
--------------------------------------------------------------------------------
/src/locales/pointsBalloon/fi.json:
--------------------------------------------------------------------------------
1 | {
2 | "gotPoints": "Saadut pisteet: ",
3 | "maxPoints": "Maksimipisteet: ",
4 | "programmingService": "Ohjelmointitehtävät",
5 | "quizService": "Kyselyt",
6 | "totalPoints": "Tehtäväpisteet yhteensä",
7 | "progressTotal": "Osasta saadut kurssipisteet: ",
8 | "noTimeLimit": "Olet aikatauluttomalla kurssilla, josta ei voi hakea opinto-oikeutta.",
9 | "canApplyForStudyRight": "Opinto-oikeuteen vaaditaan 90% aikataulutetun kurssin ohjelmointitehtävien pisteistä. Edistymisesi tällä hetkellä:",
10 | "progress":"Edistyminen (beta)",
11 | "close": "sulje",
12 | "error":"Edistymisen hakeminen kaatui seuraavaan virheeseen:"
13 | }
14 |
--------------------------------------------------------------------------------
/src/locales/user/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcomeToCourse": "Welcome to the course!",
3 | "emailSent":"We have sent a confirmation link to your e-mail address. Please check your email and confirm your address.",
4 | "emailExample":"The email should look as follows:",
5 | "emailExampleAria":"Example e-mail",
6 | "nowContinue":"After this you can continue ",
7 | "toMaterial":"to the material",
8 | "withDeadlines": "Scheduled programming MOOC",
9 | "noDeadlines": "Unscheduled programming MOOC",
10 | "beginPart8withDeadlines":"I will start the scheduled course from part 8",
11 | "beginPart8withoutDeadlines":"I will start the unscheduled course from part 8",
12 | "summerMooc":"Summer: Scheduled programming MOOC.",
13 | "summerMoocDL":"Summer: I start the scheduled course from part 8",
14 | "whichCourse":"Which courses points, deadlines and coins do you want to see?",
15 | "emailInUse":"The email is already in use. Have you done mooc.fi courses before?",
16 | "problemCreatingAccount":"Problem creating an account. The error was: ",
17 | "noAt":"The email did not contain @",
18 | "passwordsNoMatch":"Passwords did not match",
19 | "createAccount":"Create new account",
20 | "courseUses":"This course uses",
21 | "courseUses2":"accounts. If you have previously done mooc.fi -courses, you can log in with your existing account. On this page you can create a new account, which works on majority of mooc.fi courses and services.",
22 | "email":"E-mail address",
23 | "emailUsername":"e-mail or username",
24 | "password":"password",
25 | "passwordAgain":"Repeat password",
26 | "create":"Create an account",
27 | "alreadyHaveAccount":" Already have an account? Log in",
28 | "error":"Error: ",
29 | "forgottenPW":"Forgot your password?",
30 | "wrongDetails":"Something is wrong with the login details, please check them for any typos!"
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/src/locales/user/fi.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcomeToCourse": "Tervetuloa kurssille!",
3 | "emailSent":"Olemme lähettäneet sähköpostiisi sähköpostiosoitteen varmistuslinkin. Käy nyt sähköpostissasi ja varmista osoitteesi.",
4 | "emailExample":"Sähköpostin pitäisi näyttää tälläiseltä:",
5 | "emailExampleAria":"Esimerkki sähköpostista",
6 | "nowContinue":"Tämän jälkeen voit jatkaa",
7 | "toMaterial":"materiaaliin",
8 | "withDeadlines": "Aikataulutettu Ohjelmoinnin MOOC",
9 | "noDeadlines": "Aikatauluton Ohjelmoinnin MOOC",
10 | "beginPart8withDeadlines":"Aloitan kurssin aikataulutettuna osasta 8 (Ohjelmoinnin jatkokurssi)",
11 | "beginPart8withoutDeadlines":"Aloitan kurssin aikatauluttomana osasta 8 (Ohjelmoinnin jatkokurssi)",
12 | "summerMooc":"Kesä: Aikataulutettu Ohjelmoinnin MOOC",
13 | "summerMoocDL":"Kesä: Aloitan kurssin aikataulutettuna osasta 8 (Ohjelmoinninjatkokurssi)",
14 | "whichCourse":"Minkä kurssin version pisteet, deadlinet ja kolikot haluat nähdä?",
15 | "emailInUse":"Sähköpostiosoitteesi on jo käytössä. Oletko tehnyt aikaisemmin mooc.fi:n kursseja?",
16 | "problemCreatingAccount":"Ongelma tunnuksen luonnissa. Virhe oli:",
17 | "noAt":"Sähköpostiosoitessa ei ole '@'-merkkiä.",
18 | "passwordsNoMatch":"Salasana ja salasana uudestaan eivät olleet samoja.",
19 | "createAccount":"Luo käyttäjätunnus",
20 | "courseUses":"Tämä kurssi käyttää",
21 | "courseUses2":"käyttäjätunnuksia. Jos olet aikaisemmin tehnyt mooc.fi -kursseja, voit käyttää sisäänkirjautumissivulla olemassaolevia tunnuksiasi. Tällä sivulla voit luoda uuden tunnuksen, joka toimii suurimmassa osassa mooc.fi:n kursseissa ja palveluissa.",
22 | "email":"Sähköpostiosoite",
23 | "emailUsername":"Sähköpostiosoite tai käyttäjänimi",
24 | "password":"Salasana",
25 | "passwordAgain":"Salasana uudestaan",
26 | "create":"Luo käyttäjätunnus",
27 | "alreadyHaveAccount":"Onko sinulla jo käyttäjätunnus? Kirjaudu sisään",
28 | "error":"Virhe: ",
29 | "forgottenPW":"Unohditko salasanasi?",
30 | "wrongDetails":"Virheelliset käyttäjätunnukset. Tarkista kirjoitusasu!"
31 |
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Layout from "../templates/Layout"
3 | import Container from "../components/Container"
4 | import { withLoginStateContext } from "../contexes/LoginStateContext"
5 | import Helmet from "react-helmet"
6 |
7 | const NotFoundPage = () => (
8 |
9 |
10 |
11 | Ei löytynyt
12 | Olet päätynyt osoitteeseen, jota ei ole olemassa.
13 |
14 |
15 | )
16 |
17 | export default withLoginStateContext(NotFoundPage)
18 |
--------------------------------------------------------------------------------
/src/pages/missing-info.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import Layout from "../templates/Layout"
4 | import CourseOptionsEditor from "../components/user/CourseOptionsEditor"
5 | import { navigate } from "gatsby"
6 | import LoginStateContext, {
7 | withLoginStateContext,
8 | } from "../contexes/LoginStateContext"
9 | import Container from "../components/Container"
10 |
11 | class MissingInfo extends React.Component {
12 | static contextType = LoginStateContext
13 |
14 | onStepComplete = () => {
15 | if (typeof window !== "undefined") {
16 | window.history.back()
17 | return
18 | }
19 | navigate("/")
20 | }
21 |
22 | render() {
23 | if (!this.context.loggedIn) {
24 | if (typeof window !== "undefined") {
25 | navigate("/sign-in")
26 | }
27 | return Redirecting...
28 | }
29 | return (
30 |
31 |
32 |
36 |
37 |
38 | )
39 | }
40 | }
41 |
42 | export default withLoginStateContext(MissingInfo)
43 |
--------------------------------------------------------------------------------
/src/pages/sign-up.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Helmet from "react-helmet"
3 |
4 | import Layout from "../templates/Layout"
5 | import CreateAccountForm from "../components/user/CreateAccountForm"
6 | import CourseOptionsEditor from "../components/user/CourseOptionsEditor"
7 | import ConfirmEmail from "../components/user/ConfirmEmail"
8 | import LoginStateContext, {
9 | withLoginStateContext,
10 | } from "../contexes/LoginStateContext"
11 | import Container from "../components/Container"
12 |
13 | class SignInPage extends React.Component {
14 | static contextType = LoginStateContext
15 |
16 | state = {
17 | step: 1,
18 | }
19 |
20 | onStepComplete = () => {
21 | this.setState((prevState) => ({
22 | step: prevState.step + 1,
23 | }))
24 | if (typeof window !== "undefined") {
25 | window.scrollTo(0, 0)
26 | }
27 | }
28 |
29 | render() {
30 | let stepComponent
31 | if (this.state.step === 1) {
32 | stepComponent =
33 | } else if (this.state.step === 2) {
34 | stepComponent = (
35 |
39 | )
40 | } else {
41 | stepComponent =
42 | }
43 |
44 | return (
45 |
46 |
47 | {stepComponent}
48 |
49 | )
50 | }
51 | }
52 |
53 | export default withLoginStateContext(SignInPage)
54 |
--------------------------------------------------------------------------------
/src/partials/AbStudy/OnlyForAbGroup.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
3 | import AbGroupContext from "../../contexes/AbGroupContext"
4 |
5 | class OnlyForAbGroup extends Component {
6 | static contextType = AbGroupContext
7 |
8 | state = {
9 | render: false,
10 | }
11 |
12 | componentDidMount() {
13 | this.setState({ render: true })
14 | }
15 |
16 | render() {
17 | if (!this.state.render) {
18 | return Loading...
19 | }
20 | if (!this.context) {
21 | return (
22 |
23 | Error:. Please use only-for-ab-group only inside of ab-study
24 | components.
25 |
26 | )
27 | }
28 | if (!this.props.group) {
29 | return Error: please provide a group.
30 | }
31 | if (this.props.group === this.context.toString()) {
32 | return this.props.children
33 | }
34 | return
35 | }
36 | }
37 |
38 | export default withSimpleErrorBoundary(OnlyForAbGroup)
39 |
--------------------------------------------------------------------------------
/src/partials/CodeHighLight.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
3 |
4 | const CodeHighLight = () => Test
5 |
6 | export default withSimpleErrorBoundary(CodeHighLight)
7 |
--------------------------------------------------------------------------------
/src/partials/CodeStatesVisualizer.js:
--------------------------------------------------------------------------------
1 | import React, { lazy, Suspense } from "react"
2 |
3 | import "code-states-visualizer/dist/app.css"
4 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
5 | const CodeStatesVisualizer = lazy(() => import("code-states-visualizer"))
6 |
7 | class CodeStatesVisualizerWrapper extends React.Component {
8 | state = {
9 | render: false,
10 | }
11 |
12 | componentDidMount() {
13 | this.setState({ render: true })
14 | }
15 |
16 | render() {
17 | if (!this.state.render) {
18 | return Loading...
19 | }
20 | const { input } = this.props
21 | return (
22 | Loading...}>
23 |
24 |
25 | )
26 | }
27 | }
28 |
29 | export default withSimpleErrorBoundary(CodeStatesVisualizerWrapper)
30 |
--------------------------------------------------------------------------------
/src/partials/Deadline.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
4 | import { faClock as icon } from "@fortawesome/free-solid-svg-icons"
5 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
6 |
7 | const DeadlineWrapper = styled.div`
8 | margin-bottom: 1rem;
9 | color: #6c757d;
10 | font-size: 0.9rem;
11 | font-weight: bold;
12 | `
13 |
14 | const StyledIcon = styled(FontAwesomeIcon)`
15 | //vertical-align: middle;
16 | margin-right: 0.25em;
17 | font-size: 1em;
18 | `
19 |
20 | const Deadline = ({ children }) => {
21 | return (
22 |
23 |
24 | Deadline: {children}
25 |
26 | )
27 | }
28 |
29 | export default withSimpleErrorBoundary(Deadline)
30 |
--------------------------------------------------------------------------------
/src/partials/ExercisesInAllSections/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { withTranslation } from "react-i18next"
3 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
4 | import ExerciseList from "./ExerciseList"
5 |
6 | class ExercisesInAllSections extends React.Component {
7 | state = {
8 | render: false,
9 | }
10 |
11 | componentDidMount() {
12 | this.setState({ render: true })
13 | }
14 |
15 | render() {
16 | if (!this.state.render) {
17 | return Loading...
18 | }
19 | return
20 | }
21 | }
22 |
23 | export default withTranslation("common")(
24 | withSimpleErrorBoundary(ExercisesInAllSections),
25 | )
26 |
--------------------------------------------------------------------------------
/src/partials/ExercisesInThisSection/ExerciseSummary.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
3 | import styled from "styled-components"
4 | import { normalizeExerciseId } from "../../util/strings"
5 | import { Link } from "gatsby"
6 | import { withTranslation } from "react-i18next"
7 | const ExerciseSummaryWrapper = styled(Link)`
8 | padding-left: 1rem;
9 | margin-bottom: 0.5rem;
10 | display: block;
11 | `
12 |
13 | const ExerciseSummary = ({ exercise, index, quizIdToTitle, t }) => {
14 | let description = t("unknownType")
15 | if (exercise.type === "quiz") {
16 | const name = quizIdToTitle[exercise.id]
17 | if (name) {
18 | description = `${t("quiz")}: ${name}`
19 | } else {
20 | description = t("quiz")
21 | }
22 | }
23 | if (exercise.type === "programming-exercise") {
24 | description = `${t("programmingExercise")} ${exercise.id}`
25 | }
26 | if (exercise.type === "crowdsorcerer") {
27 | description = "Crowdsorcerer"
28 | }
29 | if (exercise.type === "moodle-exercise") {
30 | description = `${t("moodleExercise")} ${exercise.id}`
31 | }
32 | if (exercise.type === "sqltrainer-exercise") {
33 | description = `${t("sqlTrainerExercise")} ${exercise.id}`
34 | }
35 | if (exercise.type === "in-browser-programming-exercise") {
36 | description = `${t("programmingExercise")} ${exercise.id}`
37 | }
38 | let anchorLinkDigest = normalizeExerciseId(`${exercise.type}-${exercise.id}`)
39 | return (
40 |
43 | {index + 1}. {description}
44 |
45 | )
46 | }
47 |
48 | export default withTranslation("common")(
49 | withSimpleErrorBoundary(ExerciseSummary),
50 | )
51 |
--------------------------------------------------------------------------------
/src/partials/ExercisesInThisSection/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Accordion from "@material-ui/core/Accordion"
3 | import AccordionSummary from "@material-ui/core/AccordionSummary"
4 | import AccordionDetails from "@material-ui/core/AccordionDetails"
5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
6 | import { faAngleDown as icon } from "@fortawesome/free-solid-svg-icons"
7 | import { withTranslation } from "react-i18next"
8 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
9 | import ExerciseList from "./ExerciseList"
10 |
11 | class ExercisesInThisSection extends React.Component {
12 | state = {
13 | render: false,
14 | }
15 |
16 | componentDidMount() {
17 | this.setState({ render: true })
18 | }
19 |
20 | render() {
21 | if (!this.state.render) {
22 | return Loading...
23 | }
24 | return (
25 |
26 | }>
27 | {this.props.t("exerciseList")}
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 | }
36 |
37 | export default withTranslation("common")(
38 | withSimpleErrorBoundary(ExercisesInThisSection),
39 | )
40 |
--------------------------------------------------------------------------------
/src/partials/FloatImageRight.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
4 |
5 | const FloatImageRightContainer = styled.div``
6 |
7 | const FloatImageRight = ({ children }) => {
8 | return {children}
9 | }
10 |
11 | export default withSimpleErrorBoundary(FloatImageRight)
12 |
--------------------------------------------------------------------------------
/src/partials/GoogleFormLink.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react"
2 | import LoginStateContext from "../contexes/LoginStateContext"
3 | import styled from "styled-components"
4 | import { Card } from "@material-ui/core"
5 | import { withTranslation } from "react-i18next"
6 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
7 | import { useAsync } from "react-use"
8 | import { getCachedUserDetails } from "../services/moocfi"
9 |
10 | const Wrapper = styled(Card)`
11 | margin-bottom: 2rem;
12 | padding: 1rem;
13 | `
14 |
15 | const MessageWrapper = styled.div`
16 | display: flex;
17 | align-items: center;
18 | `
19 |
20 | const P = styled.p`
21 | margin-bottom: 1rem !important;
22 | `
23 |
24 | const GoogleFormLink = ({ children, href, t, emailfieldname }) => {
25 | const { loggedIn } = useContext(LoginStateContext)
26 | const userDetails = useAsync(getCachedUserDetails)
27 |
28 | if (!loggedIn) {
29 | return (
30 |
31 |
32 |
33 |
{t("loginToSee")}
34 |
35 |
36 |
37 | )
38 | }
39 |
40 | if (userDetails.loading) {
41 | return Loading...
42 | }
43 |
44 | if (userDetails.error) {
45 | return Error while loading user information.
46 | }
47 |
48 | const email = userDetails.value.email
49 |
50 | let link = href
51 | if (emailfieldname) {
52 | link = `${link}&${encodeURIComponent(emailfieldname)}=${encodeURIComponent(
53 | email,
54 | )}`
55 | }
56 | return (
57 |
58 | {children}
59 |
60 | )
61 | }
62 |
63 | export default withTranslation("common")(
64 | withSimpleErrorBoundary(GoogleFormLink),
65 | )
66 |
--------------------------------------------------------------------------------
/src/partials/Headers/H1.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { normalizeExerciseId } from "../../util/strings"
3 |
4 | const H1 = ({ children }) => {
5 | let text = "unknown heading"
6 | try {
7 | text = children.find((o) => typeof o === "string") || "unknown heading"
8 | } catch (e) {}
9 |
10 | const id = `${normalizeExerciseId(text)}`
11 | return (
12 |
13 | {children}
14 |
15 | )
16 | }
17 |
18 | export default H1
19 |
--------------------------------------------------------------------------------
/src/partials/Headers/H2.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { normalizeExerciseId } from "../../util/strings"
3 |
4 | const H2 = ({ children }) => {
5 | let text = "unknown heading"
6 | try {
7 | text = children.find((o) => typeof o === "string") || "unknown heading"
8 | } catch (e) {}
9 | const id = `${normalizeExerciseId(text)}`
10 | return (
11 |
12 | {children}
13 |
14 | )
15 | }
16 |
17 | export default H2
18 |
--------------------------------------------------------------------------------
/src/partials/Headers/H3.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { normalizeExerciseId } from "../../util/strings"
3 |
4 | const H3 = ({ children }) => {
5 | let text = "unknown heading"
6 | try {
7 | text = children.find((o) => typeof o === "string") || "unknown heading"
8 | } catch (e) {}
9 | const id = `${normalizeExerciseId(text)}`
10 | return (
11 |
12 | {children}
13 |
14 | )
15 | }
16 |
17 | export default H3
18 |
--------------------------------------------------------------------------------
/src/partials/Headers/H4.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { normalizeExerciseId } from "../../util/strings"
3 |
4 | const H4 = ({ children }) => {
5 | let text = "unknown heading"
6 | try {
7 | text = children.find((o) => typeof o === "string") || "unknown heading"
8 | } catch (e) {}
9 | const id = `${normalizeExerciseId(text)}`
10 | return (
11 |
12 | {children}
13 |
14 | )
15 | }
16 |
17 | export default H4
18 |
--------------------------------------------------------------------------------
/src/partials/Headers/H5.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { normalizeExerciseId } from "../../util/strings"
3 |
4 | const H5 = ({ children }) => {
5 | let text = "unknown heading"
6 | try {
7 | text = children.find((o) => typeof o === "string") || "unknown heading"
8 | } catch (e) {}
9 | const id = `${normalizeExerciseId(text)}`
10 | return (
11 |
12 | {children}
13 |
14 | )
15 | }
16 |
17 | export default H5
18 |
--------------------------------------------------------------------------------
/src/partials/Headers/H6.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { normalizeExerciseId } from "../../util/strings"
3 |
4 | const H6 = ({ children }) => {
5 | let text = "unknown heading"
6 | try {
7 | text = children.find((o) => typeof o === "string") || "unknown heading"
8 | } catch (e) {}
9 | const id = `${normalizeExerciseId(text)}`
10 | return (
11 |
12 | {children}
13 |
14 | )
15 | }
16 |
17 | export default H6
18 |
--------------------------------------------------------------------------------
/src/partials/Hr.js:
--------------------------------------------------------------------------------
1 | import { Divider } from "@material-ui/core"
2 | import styled from "styled-components"
3 |
4 | const StyledDivider = styled(Divider)`
5 | margin: 1em 16px !important;
6 | `
7 |
8 | export default StyledDivider
9 |
--------------------------------------------------------------------------------
/src/partials/InBrowserProgrammingExercise/index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, lazy, Suspense } from "react"
2 | import { Paper } from "@material-ui/core"
3 | import styled from "styled-components"
4 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
5 | import Loading from "../../components/Loading"
6 | const MoocFiPythonEditor = lazy(() => import("./MoocfiPythonEditorLoader"))
7 |
8 | const StyledPaper = styled(Paper)`
9 | @media only screen and (max-width: 800px) {
10 | overflow-y: scroll;
11 | }
12 | `
13 |
14 | class MoocFiPythonEditorWrapper extends React.Component {
15 | state = {
16 | render: false,
17 | }
18 |
19 | constructor(props) {
20 | super(props)
21 | this.linkContainer = React.createRef()
22 | }
23 |
24 | componentDidMount() {
25 | this.setState({ render: true })
26 | }
27 |
28 | render() {
29 | if (!this.state.render) {
30 | return (
31 |
32 |
33 |
34 | )
35 | }
36 | return (
37 | Loading...}>
38 |
39 |
40 |
41 |
42 | )
43 | }
44 | }
45 |
46 | export default withSimpleErrorBoundary(MoocFiPythonEditorWrapper)
47 |
--------------------------------------------------------------------------------
/src/partials/MoodleExercise.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
5 | import { faUniversity as icon } from "@fortawesome/free-solid-svg-icons"
6 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
7 | import { normalizeExerciseId } from "../util/strings"
8 |
9 | const Wrapper = styled.aside`
10 | padding 1rem;
11 | margin-bottom: 2rem;
12 | border-left: 0.2rem solid var(--color);
13 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12);
14 | border-radius: 4px;
15 | `
16 |
17 | const StyledIcon = styled(FontAwesomeIcon)`
18 | vertical-align: middle;
19 | margin-right: 1rem;
20 | margin-left: 0.5rem;
21 | color: var(--color);
22 | `
23 |
24 | const Header = styled.h3`
25 | font-size: 1.3rem;
26 | font-weight: normal;
27 | padding-bottom: 1rem;
28 | border-bottom: 1px solid #f7f7f9;
29 | `
30 |
31 | const Body = styled.div`
32 | padding-bottom: 0.5rem;
33 | `
34 |
35 | const MoodleExercise = (props) => {
36 | return (
37 |
41 |
42 |
43 | Moodle-tehtävä: {props.name}
44 |
45 | {props.children}
46 |
47 | )
48 | }
49 |
50 | export default withSimpleErrorBoundary(MoodleExercise)
51 |
--------------------------------------------------------------------------------
/src/partials/Notice.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Typography from "@material-ui/core/Typography"
3 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
4 |
5 | const Notice = (props) => {
6 | return (
7 |
8 | {props.children}
9 |
10 | )
11 | }
12 |
13 | export default withSimpleErrorBoundary(Notice)
14 |
--------------------------------------------------------------------------------
/src/partials/OnlyForCourseVariant.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
3 | import { getCourseVariant } from "../services/moocfi"
4 | import LoginStateContext from "../contexes/LoginStateContext"
5 |
6 | class OnlyForCourseVariant extends Component {
7 | static contextType = LoginStateContext
8 | state = {
9 | render: false,
10 | organization: "",
11 | course: "",
12 | }
13 |
14 | async componentDidMount() {
15 | this.setState({
16 | render: true,
17 | })
18 |
19 | const { tmcOrganization, tmcCourse } = await getCourseVariant()
20 | this.setState({ organization: tmcOrganization, course: tmcCourse })
21 | }
22 |
23 | render() {
24 | if (!this.state.render && !this.state.organization && !this.state.course) {
25 | return Loading...
26 | }
27 | if (!this.context.loggedIn) {
28 | return
29 | }
30 | if (
31 | this.props.organization === this.state.organization &&
32 | this.props.course === this.state.course
33 | ) {
34 | return this.props.children
35 | }
36 | return
37 | }
38 | }
39 |
40 | export default withSimpleErrorBoundary(OnlyForCourseVariant)
41 |
--------------------------------------------------------------------------------
/src/partials/OnlyForNotLoggedIn.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
3 | import LoginStateContext from "../contexes/LoginStateContext"
4 |
5 | class OnlyForNotLoggedIn extends Component {
6 | static contextType = LoginStateContext
7 |
8 | state = {
9 | render: false,
10 | }
11 |
12 | componentDidMount() {
13 | this.setState({
14 | render: true,
15 | })
16 | }
17 |
18 | render() {
19 | if (!this.state.render) {
20 | return Loading...
21 | }
22 | if (!this.context.loggedIn) {
23 | return {this.props.children}
24 | }
25 | return
26 | }
27 | }
28 |
29 | export default withSimpleErrorBoundary(OnlyForNotLoggedIn)
30 |
--------------------------------------------------------------------------------
/src/partials/PdfSlideshow/PdfSlideshowLoader.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import PdfSlideshow, { setPdfJsWorkerPath } from "pdf-slideshow"
3 | import { withPrefix } from "gatsby"
4 | import ReduxActionAnalytics from "redux-action-analytics"
5 | import { canDoResearch } from "../../services/moocfi"
6 | import * as storejs from "store"
7 |
8 | setPdfJsWorkerPath(withPrefix("/pdf.worker.min.js"))
9 |
10 | const PdfSlideshowLoader = (props) => {
11 | const middleware = []
12 | const analytics = new ReduxActionAnalytics(
13 | "https://usage.testmycode.io/api/v0/data",
14 | "pdf-slideshow",
15 | props.pdfLocation,
16 | 10000,
17 | () => {
18 | const user = storejs.get("tmc.user")
19 | if (user === undefined) {
20 | return {}
21 | }
22 | return {
23 | username: user.username,
24 | }
25 | },
26 | )
27 | if (canDoResearch()) {
28 | middleware.push(analytics.getMiddleware())
29 | }
30 | return
31 | }
32 |
33 | export default PdfSlideshowLoader
34 |
--------------------------------------------------------------------------------
/src/partials/PdfSlideshow/index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, lazy, Suspense } from "react"
2 | import { Paper } from "@material-ui/core"
3 | import styled from "styled-components"
4 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
5 | import Loading from "../../components/Loading"
6 | const PdfSlideshow = lazy(() => import("./PdfSlideshowLoader"))
7 |
8 | const HiddenLinkWrapper = styled.div`
9 | display: none;
10 | `
11 |
12 | const StyledPaper = styled(Paper)`
13 | @media only screen and (max-width: 800px) {
14 | overflow-y: scroll;
15 | }
16 | `
17 |
18 | class PdfSlideshowWrapper extends React.Component {
19 | state = {
20 | render: false,
21 | path: undefined,
22 | }
23 |
24 | constructor(props) {
25 | super(props)
26 | this.linkContainer = React.createRef()
27 | }
28 |
29 | componentDidMount() {
30 | const links = this.linkContainer.current
31 | const path = links.querySelector("a").href
32 | this.setState({ render: true, path })
33 | }
34 |
35 | render() {
36 | if (!this.state.render) {
37 | return (
38 |
39 |
40 |
41 | {this.props.children}
42 |
43 |
44 | )
45 | }
46 | return (
47 | Loading...}>
48 |
49 |
50 |
51 |
52 | )
53 | }
54 | }
55 |
56 | export default withSimpleErrorBoundary(PdfSlideshowWrapper)
57 |
--------------------------------------------------------------------------------
/src/partials/PleaseLogin.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Card, CardContent } from "@material-ui/core"
3 | import styled from "styled-components"
4 | import LoginControls from "../components/LoginControls"
5 | import LoginStateContext from "../contexes/LoginStateContext"
6 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
7 | import { withTranslation } from "react-i18next"
8 | const PleaseLoginWrapper = styled(Card)`
9 | margin-bottom: 2rem;
10 | `
11 |
12 | const Wrapper = styled.div`
13 | margin-bottom: 1rem;
14 | `
15 | class PleaseLogin extends React.Component {
16 | static contextType = LoginStateContext
17 |
18 | render() {
19 | if (this.context.loggedIn) {
20 | return
21 | }
22 | return (
23 |
24 |
25 | {this.props.t("pleaseLogin")}
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 | }
34 |
35 | export default withTranslation("common")(withSimpleErrorBoundary(PleaseLogin))
36 |
--------------------------------------------------------------------------------
/src/partials/Points/OverallPoints.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Card, CardContent, Typography, Button } from "@material-ui/core"
3 | import { LinearProgress } from "@material-ui/core"
4 | import styled from "styled-components"
5 |
6 | const ProgressLineContainer = styled.div`
7 | display: flex;
8 | align-items: center;
9 | margin: 1rem 0;
10 | div {
11 | height: 20px;
12 | }
13 | `
14 |
15 | const StyledLinearProgress = styled(LinearProgress)`
16 | flex: 1;
17 | margin-left: 10px;
18 | `
19 |
20 | const OverallPoints = ({ courseName, progress, refetch }) => {
21 | const data = progress.user_course_progress.progress.sort((a, b) =>
22 | a.group.localeCompare(b.group, undefined, {
23 | numeric: true,
24 | sensitivity: "base",
25 | }),
26 | )
27 | return (
28 |
29 |
30 |
31 | {courseName}
32 |
33 |
34 | {data.map((group) => {
35 | return (
36 | <>
37 |
38 | {group.group}
39 |
43 |
44 | >
45 | )
46 | })}
47 |
54 |
55 |
56 | )
57 | }
58 |
59 | export default OverallPoints
60 |
--------------------------------------------------------------------------------
/src/partials/Points/PartPoints.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Card, CardContent, Typography } from "@material-ui/core"
3 |
4 | export default ({ points }) => (
5 |
6 |
7 |
8 | Pisteet: {points.group}
9 |
10 |
11 |
12 | )
13 |
--------------------------------------------------------------------------------
/src/partials/Points/PointsImpl.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
3 |
4 | import { useQuery } from "@apollo/react-hooks"
5 | import { gql } from "apollo-boost"
6 | import { Button } from "@material-ui/core"
7 | import OverallPoints from "./OverallPoints"
8 |
9 | const PROGRESS = gql`
10 | {
11 | currentUser {
12 | email
13 | progress(course_id: "59314cb4-a9ca-43c9-b9a7-58c19325b44c") {
14 | course {
15 | id
16 | name
17 | }
18 | user_course_progress {
19 | id
20 | progress
21 | }
22 | user_course_service_progresses {
23 | id
24 | progress
25 | service {
26 | id
27 | name
28 | }
29 | }
30 | }
31 | }
32 | }
33 | `
34 |
35 | const Points = (props) => {
36 | // const course = props.course || CourseSettings.slug
37 | const { data, loading, error, refetch } = useQuery(PROGRESS)
38 |
39 | if (loading) {
40 | return <>Loading...>
41 | }
42 |
43 | if (error) {
44 | return <>Error while fetching progress: {error}>
45 | }
46 |
47 | if (!data || !data.currentUser) {
48 | return (
49 | <>
50 |
57 | Please log in to see your points.
58 | >
59 | )
60 | }
61 |
62 | // const points = data.currentUser.progress.user_course_progress.progress[0]
63 | const courseName = data.currentUser.progress.course.name
64 | return (
65 | <>
66 |
71 | >
72 | )
73 | }
74 |
75 | export default withSimpleErrorBoundary(Points)
76 |
--------------------------------------------------------------------------------
/src/partials/Points/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import PointsImpl from "./PointsImpl"
3 | import ApolloClient from "apollo-boost"
4 | import { ApolloProvider } from "@apollo/react-hooks"
5 | import { accessToken } from "../../services/moocfi"
6 |
7 | export default class Points extends React.Component {
8 | state = {
9 | render: false,
10 | }
11 | componentDidMount() {
12 | this.setState({ render: true })
13 | }
14 | render() {
15 | if (!this.state.render) {
16 | return Loading...
17 | }
18 | const apolloClient = new ApolloClient({
19 | uri: "https://www.mooc.fi/api",
20 | request: async (operation) => {
21 | const token = accessToken()
22 | if (!token) {
23 | return
24 | }
25 | operation.setContext({
26 | headers: {
27 | Authorization: `Bearer ${token}`,
28 | },
29 | })
30 | },
31 | })
32 | return (
33 |
34 | {" "}
35 |
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/partials/ProgrammingExercise/ExerciseDescription.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const ExerciseDescriptionWrapper = styled.div`
5 | counter-reset: headingCounter;
6 |
7 | h1::before,
8 | h2::before,
9 | h3::before,
10 | h4::before,
11 | h5::before {
12 | content: "Part " counter(headingCounter) ": ";
13 | counter-increment: headingCounter;
14 | }
15 | `
16 |
17 | const ExerciseDescription = ({ children }) => (
18 |
19 |
20 | {children}
21 |
22 | )
23 |
24 | export default ExerciseDescription
25 |
--------------------------------------------------------------------------------
/src/partials/Quiznator.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
4 | import { normalizeExerciseId } from "../util/strings"
5 |
6 | const quizWrapper = styled.div`
7 | code {
8 | color: black !important;
9 | }
10 | `
11 |
12 | class Quiznator extends React.Component {
13 | componentDidMount() {
14 | const { id } = this.props
15 | if (!id || typeof window === "undefined") {
16 | return
17 | }
18 | if (!window.loadQuiz) {
19 | return
20 | }
21 | window.loadQuiz(document.getElementById(`unloaded-quiz-${id}`))
22 | }
23 |
24 | render() {
25 | const { id } = this.props
26 | if (!id) {
27 | return There should be quiz here but no quiz id is specified.
28 | }
29 | return (
30 |
31 |
36 |
37 | )
38 | }
39 | }
40 |
41 | export default withSimpleErrorBoundary(Quiznator)
42 |
--------------------------------------------------------------------------------
/src/partials/RegistrationLink.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import moment from "moment"
3 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
4 |
5 | const start = moment("01/01/2018", "DD/MM/YYYY")
6 | const summerStart = moment("18/03/2019", "DD/MM/YYYY")
7 | const autumnStart = moment("06/05/2019", "DD/MM/YYYY")
8 |
9 | const springLink =
10 | "https://www.avoin.helsinki.fi/palvelut/esittely.aspx?o=127404110"
11 | const summerLink =
12 | "https://www.avoin.helsinki.fi/palvelut/esittely.aspx?o=127404483"
13 | // const autumnLink =
14 | // "https://www.avoin.helsinki.fi/palvelut/esittely.aspx?o=127404483"
15 |
16 | function getLink() {
17 | const currentDate = moment()
18 | if (currentDate.isBetween(start, summerStart)) {
19 | return springLink
20 | }
21 | if (currentDate.isBetween(start, autumnStart)) {
22 | return summerLink
23 | }
24 | return springLink
25 | }
26 |
27 | const RegistrationLink = () => {
28 | return {getLink()}
29 | }
30 |
31 | export default withSimpleErrorBoundary(RegistrationLink)
32 |
--------------------------------------------------------------------------------
/src/partials/RegistrationLink2.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import moment from "moment"
3 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
4 |
5 | const start = moment("01/01/2018", "DD/MM/YYYY")
6 | const summerStart = moment("20/05/2019", "DD/MM/YYYY")
7 | const autumnStart = moment("05/07/2019", "DD/MM/YYYY")
8 |
9 | const springLink =
10 | "https://www.avoin.helsinki.fi/palvelut/esittely.aspx?o=127404483"
11 | const summerLink =
12 | "https://www.avoin.helsinki.fi/palvelut/esittely.aspx?o=127404483"
13 | // const autumnLink =
14 | // "https://www.avoin.helsinki.fi/palvelut/esittely.aspx?o=127404483"
15 |
16 | function getLink() {
17 | const currentDate = moment()
18 | if (currentDate.isBetween(start, summerStart)) {
19 | return springLink
20 | }
21 | if (currentDate.isBetween(start, autumnStart)) {
22 | return summerLink
23 | }
24 | return springLink
25 | }
26 |
27 | const RegistrationLink2 = () => {
28 | return {getLink()}
29 | }
30 |
31 | export default withSimpleErrorBoundary(RegistrationLink2)
32 |
--------------------------------------------------------------------------------
/src/partials/SampleData.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
4 | import { withTranslation } from "react-i18next"
5 |
6 | const accentColor = "#CCC"
7 |
8 | const Wrapper = styled.div`
9 | padding 1rem;
10 | padding-top: 0.2rem;
11 | margin-bottom: 2rem;
12 | border-left: 0.2rem solid ${accentColor};
13 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12);
14 | border-radius: 4px;
15 | `
16 |
17 | const Body = styled.div`
18 | padding-bottom: 0.5rem;
19 | font-family: monospace;
20 | white-space: pre-wrap;
21 |
22 | p:last-of-type {
23 | margin-bottom: 0;
24 | }
25 |
26 | em,
27 | strong {
28 | color: red;
29 | font-weight: normal;
30 | }
31 | `
32 |
33 | const Note = styled.div`
34 | width: 100%;
35 | text-align: right;
36 | font-size: 0.75rem;
37 | `
38 |
39 | const SampleData = (props) => {
40 | return (
41 |
42 | {props.t("sampleData")}
43 | {props.children}
44 |
45 | )
46 | }
47 |
48 | export default withTranslation("common")(withSimpleErrorBoundary(SampleData))
49 |
--------------------------------------------------------------------------------
/src/partials/SampleOutput.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
4 | import { withTranslation } from "react-i18next"
5 |
6 | const accentColor = "#CCC"
7 |
8 | const Wrapper = styled.div`
9 | padding 1rem;
10 | padding-top: 0.2rem;
11 | margin-bottom: 2rem;
12 | border-left: 0.2rem solid ${accentColor};
13 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12);
14 | border-radius: 4px;
15 | `
16 |
17 | const Body = styled.div`
18 | padding-bottom: 0.5rem;
19 | font-family: monospace;
20 | white-space: pre-wrap;
21 |
22 | p:last-of-type {
23 | margin-bottom: 0;
24 | }
25 |
26 | em,
27 | strong {
28 | color: red;
29 | font-weight: normal;
30 | }
31 | `
32 |
33 | const Note = styled.div`
34 | width: 100%;
35 | text-align: right;
36 | font-size: 0.75rem;
37 | `
38 |
39 | const SampleOutput = (props) => {
40 | return (
41 |
42 | {props.t("sampleOutput")}
43 | {props.children}
44 |
45 | )
46 | }
47 |
48 | export default withTranslation("common")(withSimpleErrorBoundary(SampleOutput))
49 |
--------------------------------------------------------------------------------
/src/partials/SqlTrainerExercise.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
5 | import { faDatabase as icon } from "@fortawesome/free-solid-svg-icons"
6 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
7 | import { normalizeExerciseId } from "../util/strings"
8 |
9 | const Wrapper = styled.aside`
10 | padding 1rem;
11 | margin-bottom: 2rem;
12 | border-left: 0.2rem solid var(--color);
13 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12);
14 | border-radius: 4px;
15 | `
16 |
17 | const StyledIcon = styled(FontAwesomeIcon)`
18 | vertical-align: middle;
19 | margin-right: 1rem;
20 | margin-left: 0.5rem;
21 | color: var(--color);
22 | `
23 |
24 | const Header = styled.h3`
25 | font-size: 1.3rem;
26 | font-weight: normal;
27 | padding-bottom: 1rem;
28 | border-bottom: 1px solid #f7f7f9;
29 | `
30 |
31 | const Body = styled.div`
32 | padding-bottom: 0.5rem;
33 | `
34 |
35 | const SqltrainerExercise = (props) => {
36 | return (
37 |
41 |
42 |
43 | SQL Trainer -tehtävä: {props.name}
44 |
45 |
46 | {props.children}
47 | Löydät harjoittelujärjestelmän osoitteesta{" "}
48 |
53 | https://sql-t.herokuapp.com/
54 |
55 |
56 |
57 | )
58 | }
59 |
60 | export default withSimpleErrorBoundary(SqltrainerExercise)
61 |
--------------------------------------------------------------------------------
/src/partials/Summary.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
4 | import { withTranslation } from "react-i18next"
5 |
6 | const Wrapper = styled.aside`
7 | border-left: 0.2rem solid var(--color);
8 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2),
9 | 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12);
10 | border-radius: 4px;
11 | `
12 |
13 | const Header = styled.h3`
14 | font-size: 1.3rem;
15 | font-weight: normal;
16 | padding: 0.5rem;
17 | margin: 0.5rem 1rem;
18 | `
19 |
20 | const Body = styled.div`
21 | margin: 0.5rem 1rem;
22 | `
23 |
24 | const Split = styled.div`
25 | border-bottom: 1px solid #bdc3c7;
26 | `
27 |
28 | const Summary = (props) => {
29 | const updatedPropsChildren = props.children.map((child, index) => {
30 | return child.type.displayName === "Hr__StyledDivider" ? (
31 |
32 | ) : (
33 | {child}
34 | )
35 | })
36 | return (
37 |
38 | Tässä osiossa käsiteltiin seuraavat asiat:
39 |
40 | {updatedPropsChildren}
41 |
42 | )
43 | }
44 |
45 | export default withTranslation("common")(withSimpleErrorBoundary(Summary))
46 |
--------------------------------------------------------------------------------
/src/partials/Table.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import Paper from "@material-ui/core/Paper"
4 |
5 | import MaterialTable from "@material-ui/core/Table"
6 | import MaterialTableBody from "@material-ui/core/TableBody"
7 | import MaterialTableCell from "@material-ui/core/TableCell"
8 | import MaterialTableHead from "@material-ui/core/TableHead"
9 | import MaterialTableRow from "@material-ui/core/TableRow"
10 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
11 |
12 | const StyledTableCell = styled(MaterialTableCell)`
13 | padding: 1rem !important;
14 | `
15 |
16 | export const Table = withSimpleErrorBoundary((props) => (
17 |
18 |
19 |
20 | ))
21 |
22 | export const TableBody = withSimpleErrorBoundary((props) => (
23 |
24 | ))
25 |
26 | export const TableCell = withSimpleErrorBoundary((props) => (
27 |
28 | ))
29 |
30 | export const TableTh = withSimpleErrorBoundary((props) => (
31 |
32 | ))
33 |
34 | export const TableHead = withSimpleErrorBoundary((props) => (
35 |
36 | ))
37 |
38 | export const TableRow = withSimpleErrorBoundary((props) => (
39 |
40 | ))
41 |
--------------------------------------------------------------------------------
/src/partials/TableOfContents.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import Loading from "../components/Loading"
3 | import styled from "styled-components"
4 | import { Paper } from "@material-ui/core"
5 | import { normalizeExerciseId } from "../util/strings"
6 | import { withTranslation } from "react-i18next"
7 |
8 | const TableOfContentsWrapper = styled(Paper)`
9 | padding: 1rem;
10 | margin: 2rem 0;
11 | `
12 |
13 | class TableOfContents extends Component {
14 | state = { data: null }
15 |
16 | componentDidMount() {
17 | const data = Array.from(
18 | document.querySelectorAll(
19 | "h1.material-header,h2.material-header,h3.material-header",
20 | ),
21 | ).map((o) => {
22 | return o.textContent || o.innerText
23 | })
24 | this.setState({ data })
25 | }
26 |
27 | render() {
28 | return (
29 |
30 | {this.props.t("tableOfContents")}
31 |
32 | {this.state.data ? (
33 |
34 | {this.state.data.map((o) => {
35 | return (
36 | -
37 | {o}
38 |
39 | )
40 | })}
41 |
42 | ) : (
43 |
44 | )}
45 |
46 |
47 | )
48 | }
49 | }
50 |
51 | export default withTranslation("common")(TableOfContents)
52 |
--------------------------------------------------------------------------------
/src/partials/Test.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
3 |
4 | const Test = (props) => {
5 | return {JSON.stringify(props, null, 2)}
6 | }
7 |
8 | export default withSimpleErrorBoundary(Test)
9 |
--------------------------------------------------------------------------------
/src/partials/Test2.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
3 |
4 | const Test = (props) => {
5 | return {props.children}
6 | }
7 |
8 | export default withSimpleErrorBoundary(Test)
9 |
--------------------------------------------------------------------------------
/src/partials/TextBox.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
5 | import { faInfoCircle, faUserGraduate } from "@fortawesome/free-solid-svg-icons"
6 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
7 |
8 | const Wrapper = styled.aside`
9 | padding 1rem;
10 | margin-bottom: 2rem;
11 | border-left: 0.2rem solid var(--color);
12 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12);
13 | border-radius: 4px;
14 | `
15 |
16 | const StyledIcon = styled(FontAwesomeIcon)`
17 | vertical-align: middle;
18 | margin-right: 1rem;
19 | margin-left: 0.5rem;
20 | color: var(--color);
21 | `
22 |
23 | const Header = styled.h3`
24 | font-size: 1.3rem;
25 | font-weight: normal;
26 | padding-bottom: 1rem;
27 | border-bottom: 1px solid #f7f7f9;
28 | `
29 |
30 | const Body = styled.div`
31 | padding-bottom: 0.5rem;
32 | `
33 |
34 | const variantToColor = {
35 | hint: "#528afc",
36 | learningObjectives: "#57b181",
37 | }
38 |
39 | const variantToIcon = {
40 | hint: faInfoCircle,
41 | learningObjectives: faUserGraduate,
42 | }
43 |
44 | const TextBox = (props) => {
45 | return (
46 |
47 |
48 |
49 | {props.name}
50 |
51 | {props.children}
52 |
53 | )
54 | }
55 |
56 | export default withSimpleErrorBoundary(TextBox)
57 |
--------------------------------------------------------------------------------
/src/partials/Vocabulary/VocabularyWord.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
3 |
4 | const VocabularyWord = ({ name, description }) => {
5 | const anchor = name.toLowerCase().replace(" ", "-")
6 | console.log(anchor)
7 | return
8 | }
9 |
10 | export default withSimpleErrorBoundary(VocabularyWord)
11 |
--------------------------------------------------------------------------------
/src/partials/Vocabulary/Word.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import withSimpleErrorBoundary from "../../util/withSimpleErrorBoundary"
3 | import { withTranslation } from "react-i18next"
4 | import { AnchorLink } from "gatsby-plugin-anchor-links"
5 | import styled from "styled-components"
6 |
7 | const StyledWord = styled.div`
8 | margin-bottom: 0.5em;
9 | `
10 |
11 | const StyledLink = styled(AnchorLink)`
12 | margin-left: 0.5em;
13 | `
14 |
15 | const transformParentPagePath = (parentPagePath) => {
16 | const sectionNumber = parentPagePath.split("-")[1].replace("/", ".")
17 | return sectionNumber
18 | }
19 |
20 | const transformNameToAnchor = (name) => {
21 | return name.toLowerCase().replace(" ", "-")
22 | }
23 |
24 | const Word = ({ word }) => {
25 | return (
26 |
27 | {word.name},
28 | {Array.isArray(word.parentPagePath) ? (
29 | word.parentPagePath.map((path, i) => (
30 |
34 | {transformParentPagePath(path)}
35 |
36 | ))
37 | ) : (
38 |
41 | {transformParentPagePath(word.parentPagePath)}
42 |
43 | )}
44 | {word.description &&
45 | (Array.isArray(word.description) ? (
46 | word.description.map((d, i) => - {d}
)
47 | ) : (
48 | - {word.description}
49 | ))}
50 |
51 | )
52 | }
53 |
54 | export default withTranslation("common")(withSimpleErrorBoundary(Word))
55 |
--------------------------------------------------------------------------------
/src/partials/WorkshopSchedule.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import withSimpleErrorBoundary from "../util/withSimpleErrorBoundary"
3 | import CourseSettings from "../../course-settings"
4 |
5 | const WorkShopSchedule = ({ slug }) => {
6 | const language = CourseSettings.language
7 | return (
8 |
17 | )
18 | }
19 |
20 | export default withSimpleErrorBoundary(WorkShopSchedule)
21 |
--------------------------------------------------------------------------------
/src/services/abstudio.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import { accessToken } from "./moocfi"
3 |
4 | const BASE_URL = "https://ab-studio.testmycode.io"
5 |
6 | export async function fetchAbGroup(studyId) {
7 | const res = await axios.get(
8 | `${BASE_URL}/api/v0/ab_studies/${studyId}/group?oauth_token=${accessToken()}`,
9 | )
10 | return res.data
11 | }
12 |
--------------------------------------------------------------------------------
/src/services/progress.js:
--------------------------------------------------------------------------------
1 | import { fetchProgrammingProgress } from "./moocfi"
2 | import { zip } from "../util/arrays"
3 | import { fetchQuizzesProgress } from "./quizzes"
4 |
5 | //eslint-disable-next-line no-unused-vars
6 | const introductionCourseGroups = [
7 | "osa01",
8 | "osa02",
9 | "osa03",
10 | "osa04",
11 | "osa05",
12 | "osa06",
13 | "osa07",
14 | ]
15 |
16 | export async function fetchProgress(t) {
17 | // await fetchQuizzesProgress()
18 | const serviceIdentifiers = [t("programmingService"), t("quizService")]
19 | const progressesCollection = await Promise.all([
20 | fetchProgrammingProgress(),
21 | fetchQuizzesProgress(),
22 | ])
23 | // const userDetails = await getCachedUserDetails()
24 | // const currentCourseVariant = userDetails?.extra_fields?.course_variant
25 | const progressByGroup = {}
26 |
27 | zip(serviceIdentifiers, progressesCollection).forEach(
28 | ([identifier, progresses]) => {
29 | console.log(JSON.stringify(progresses))
30 | progresses.forEach((progressEntry) => {
31 | if (!progressByGroup[progressEntry.group]) {
32 | progressByGroup[progressEntry.group] = {}
33 | }
34 | progressByGroup[progressEntry.group][identifier] = progressEntry
35 | })
36 | },
37 | )
38 | const toBeDeleted = []
39 | Object.entries(progressByGroup).forEach(([group, serviceEntries]) => {
40 | if (
41 | !Object.keys(serviceEntries).find((o) => o === t("programmingService"))
42 | ) {
43 | toBeDeleted.push(group)
44 | }
45 | })
46 | // TODO: this is not a good way to do this
47 | toBeDeleted.forEach((o) => {
48 | delete progressByGroup[o]
49 | })
50 | return progressByGroup
51 | }
52 |
--------------------------------------------------------------------------------
/src/services/quizzes.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import { accessToken, getCourseVariant } from "./moocfi"
3 | import CourseSettings from "../../course-settings"
4 |
5 | // const id = CourseSettings.quizzesId
6 | const language = CourseSettings.language
7 |
8 | // const quizzesLanguage = language === "en" ? "en_US" : "fi_FI"
9 |
10 | export async function fetchQuizzesProgress() {
11 | const { quizzesId } = await getCourseVariant()
12 | const response = await axios.get(
13 | `https://quizzes.mooc.fi/api/v2/general/course/${quizzesId}/progress`,
14 | { headers: { Authorization: `Bearer ${accessToken()}` } },
15 | )
16 | return response.data
17 | }
18 |
19 | export async function fetchQuizNames() {
20 | const { quizzesId } = await getCourseVariant()
21 | const response = await axios.get(
22 | `https://quizzes.mooc.fi/api/v2/general/course/${quizzesId}/quiz-titles`,
23 | )
24 | return response.data
25 | }
26 |
--------------------------------------------------------------------------------
/src/templates/remark.css:
--------------------------------------------------------------------------------
1 | code {
2 | font-family: 'Roboto Mono', monospace;
3 | }
4 |
5 | .gatsby-highlight {
6 | margin-bottom: 2rem;
7 | }
8 |
9 | .gatsby-highlight pre {
10 | border-radius: 10px;
11 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12);
12 | }
13 |
14 | .gatsby-highlight code.language-text {
15 | padding: 0.2rem !important;
16 | margin-right: 0.4rem;
17 | padding-left: 0.4rem !important;
18 | }
19 |
20 | .gatsby-highlight code, .gatsby-highlight pre {
21 | background-color: white !important;
22 | }
23 |
24 | :not(pre) > code[class*='language-'] {
25 | background-color: rgb(243, 243, 243) !important;
26 | color: rgb(20, 20, 20) !important;
27 | white-space: pre-wrap;
28 | }
29 |
30 |
31 | @media only screen and (min-width: 550px) {
32 | .singleline-code > code {
33 | white-space: nowrap !important;
34 | }
35 | }
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/theme.js:
--------------------------------------------------------------------------------
1 | import { red, blue } from "@material-ui/core/colors"
2 | import { createMuiTheme } from "@material-ui/core/styles"
3 |
4 | // A custom theme for this app
5 | const theme = createMuiTheme({
6 | palette: {
7 | primary: {
8 | light: blue[300],
9 | main: blue[500],
10 | dark: blue[700],
11 | },
12 | secondary: {
13 | light: red[300],
14 | main: red[500],
15 | dark: red[700],
16 | },
17 | },
18 | typography: {
19 | useNextVariants: true,
20 | },
21 | overrides: {
22 | MuiButton: {
23 | label: {
24 | textTransform: "none",
25 | },
26 | root: {
27 | textTransform: "none",
28 | },
29 | },
30 | MuiTypography: {
31 | body2: {
32 | fontSize: "16px",
33 | },
34 | subheading: {
35 | fontSize: "18px",
36 | },
37 | },
38 | },
39 | })
40 |
41 | export default theme
42 |
--------------------------------------------------------------------------------
/src/util/arrays.js:
--------------------------------------------------------------------------------
1 | export function flatten(arr) {
2 | return arr.reduce(function (flat, toFlatten) {
3 | return flat.concat(
4 | Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten,
5 | )
6 | }, [])
7 | }
8 |
9 | export function zip(arr, ...arrs) {
10 | return arr.map((val, i) => arrs.reduce((a, arr) => [...a, arr[i]], [val]))
11 | }
12 |
13 | export function getCommonElements(array1, array2) {
14 | return array1.filter((value) => -1 !== array2.indexOf(value))
15 | }
16 |
--------------------------------------------------------------------------------
/src/util/constants.js:
--------------------------------------------------------------------------------
1 | export const SMALL_MEDIUM_BREAKPOINT = "900px"
2 | export const MEDIUM_LARGE_BREAKPOINT = "1200px"
3 | export const MEDIUM_SIDEBAR_WIDTH = "245px"
4 | export const LARGE_SIDEBAR_WIDTH = "324px"
5 |
--------------------------------------------------------------------------------
/src/util/dom.js:
--------------------------------------------------------------------------------
1 | export function tryToScrollToSelector(selector) {
2 | if (typeof window === "undefined") {
3 | return
4 | }
5 | try {
6 | const element = document.querySelector(selector)
7 | if (!element) {
8 | console.warn("Could not find the element to scroll to.")
9 | return
10 | }
11 | element.scrollIntoView()
12 | } catch (e) {
13 | console.warn("Could not scroll element into view", e)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/util/strings.js:
--------------------------------------------------------------------------------
1 | export function nthIndex(str, pat, n) {
2 | var L = str.length,
3 | i = -1
4 | while (n-- && i++ < L) {
5 | i = str.indexOf(pat, i)
6 | if (i < 0) break
7 | }
8 | return i
9 | }
10 |
11 | export function extractPartNumberFromPath(string) {
12 | // Assumes path is formatted /part-[num] or /part-[num]/...
13 | const subpartSeperator = nthIndex(string, "/", 2)
14 | if (subpartSeperator !== -1) {
15 | string = string.substring(0, subpartSeperator)
16 | }
17 | return parseInt(string.substring(string.indexOf("-") + 1))
18 | }
19 |
20 | export function extractSubpartNumberFromPath(string) {
21 | // Assumes path is formatted /part-[num]/[num]-...
22 | return parseInt(
23 | string.substring(nthIndex(string, "/", 2) + 1, nthIndex(string, "-", 2)),
24 | )
25 | }
26 |
27 | export function capitalizeFirstLetter(string) {
28 | return string.charAt(0).toUpperCase() + string.slice(1)
29 | }
30 |
31 | export function removeLeadingZeros(string) {
32 | return string.replace(/^0+/, "")
33 | }
34 |
35 | export function splitGroupNameToWordAndNumber(string) {
36 | return string.split(/(\d+)/)
37 | }
38 |
39 | export function improveGroupName(string) {
40 | var stringParts = splitGroupNameToWordAndNumber(string)
41 | return (
42 | capitalizeFirstLetter(stringParts[0]) +
43 | " " +
44 | removeLeadingZeros(stringParts[1])
45 | )
46 | }
47 |
48 | export function normalizeExerciseId(string) {
49 | return encodeURIComponent(
50 | string
51 | .toLowerCase()
52 | .replace(/ö/g, "o")
53 | .replace(/Ö/g, "O")
54 | .replace(/ä/g, "a")
55 | .replace(/Ä/g, "A")
56 | .replace(/\s+/g, "-")
57 | .replace(/[^A-Za-z0-9_-]/g, "")
58 | .replace(/-+/g, "-"),
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/src/util/trackHeight.js:
--------------------------------------------------------------------------------
1 | // Used for animations
2 | import { createGlobalStyle } from "styled-components"
3 |
4 | createGlobalStyle`
5 | .render-element-off-screen-for-measurement {
6 | position: absolute !important;
7 | top: -100000px !important;
8 | height: auto !important;
9 | }
10 | `
11 |
12 | const saveHeight = (element, height) => {
13 | element.style.setProperty("--calculated-height", height)
14 | }
15 |
16 | const calculateElementHeightOffScreen = (element) => {
17 | return new Promise((resolve) => {
18 | element.classList.add("render-element-off-screen-for-measurement")
19 | setTimeout(() => {
20 | const height = element.getBoundingClientRect().height
21 | element.classList.remove("render-element-off-screen-for-measurement")
22 | resolve(height)
23 | }, 100)
24 | })
25 | }
26 |
27 | const calculateElementHeight = async (element) => {
28 | let { height } = element.getBoundingClientRect()
29 | if (height === 0) {
30 | height = await calculateElementHeightOffScreen(element)
31 | }
32 | saveHeight(element, height)
33 | }
34 |
35 | export const trackElementHeight = (element) => {
36 | if (element === null) {
37 | return
38 | }
39 | element.classList.add("track-element-height-changes-for-animations")
40 | calculateElementHeight(element)
41 | }
42 |
43 | const trackHeight = () => {
44 | window.addEventListener("resize", () => {
45 | document
46 | .querySelectorAll(".track-element-height-changes-for-animations")
47 | .forEach((e) => {
48 | calculateElementHeight(e)
49 | })
50 | })
51 | }
52 |
53 | export default trackHeight
54 |
--------------------------------------------------------------------------------
/src/util/withSimpleErrorBoundary.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export default function withSimpleErrorBoundary(Component) {
4 | class SimpleErrorBoundary extends React.Component {
5 | state = {
6 | error: null,
7 | }
8 |
9 | static getDerivedStateFromError(error) {
10 | return { error: error.toString() }
11 | }
12 |
13 | componentDidCatch(error, info) {
14 | console.error(error, info)
15 | }
16 |
17 | render() {
18 | if (this.state.error) {
19 | return (
20 |
21 | Ohjelman osa kaatui:
22 |
{this.state.error}
23 |
24 | )
25 | }
26 |
27 | return
28 | }
29 | }
30 | return SimpleErrorBoundary
31 | }
32 |
--------------------------------------------------------------------------------
/update.rb:
--------------------------------------------------------------------------------
1 | updates = File.read("updates")
2 |
3 | paths = `find data -name '*.md'`.split("\n")
4 |
5 | paths.each do |path|
6 | input = File.read(path)
7 |
8 | updates.split("\n").each do |line|
9 | split = line.split(" ")
10 | old_id = split[0]
11 | new_id = split[1]
12 | input = input.gsub(old_id, new_id)
13 | end
14 |
15 | File.write(path, input)
16 | end
17 |
--------------------------------------------------------------------------------
/use-local-code-states-visualizer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PATH_TO_PACKAGE=${1:-"../code-states-visualizer"};
4 |
5 |
6 | if [ ! -d "$PATH_TO_PACKAGE" ]; then
7 | echo "Given argument '$PATH_TO_PACKAGE' is not a directory"
8 | exit 1
9 | fi
10 |
11 | rm -r ./node_modules/code-states-visualizer;
12 | mkdir ./node_modules/code-states-visualizer;
13 |
14 | if [ ! -d "$PATH_TO_PACKAGE/dist" ]; then
15 | PATH_TO_THIS=$PWD
16 | cd "$PATH_TO_PACKAGE" || exit 1
17 | yarn install
18 | cd "$PATH_TO_THIS" || exit 1
19 | fi
20 |
21 | cp -r "$PATH_TO_PACKAGE/dist" node_modules/code-states-visualizer/dist;
22 | cp "$PATH_TO_PACKAGE/package.json" node_modules/code-states-visualizer/package.json;
23 | cd node_modules/code-states-visualizer || exit 1;
24 | # npm install --prod;
25 | cd ../..;
26 |
--------------------------------------------------------------------------------
/use-local-quizzes.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PATH_TO_PACKAGE=${1:-"../quizzes/packages/moocfi-quizzes"};
4 |
5 |
6 | if [ ! -d "$PATH_TO_PACKAGE" ]; then
7 | echo "Given argument '$PATH_TO_PACKAGE' is not a directory"
8 | exit -1
9 | fi
10 |
11 | rm -r ./node_modules/moocfi-quizzes;
12 | mkdir ./node_modules/moocfi-quizzes;
13 |
14 | if [ ! -d "$PATH_TO_PACKAGE/dist" ]; then
15 | PATH_TO_THIS=$PWD
16 | cd $PATH_TO_PACKAGE
17 | yarn install
18 | cd $PATH_TO_THIS
19 | fi
20 |
21 | cp -r $PATH_TO_PACKAGE/dist node_modules/moocfi-quizzes/dist;
22 | cp $PATH_TO_PACKAGE/package.json node_modules/moocfi-quizzes/package.json;
23 | cd node_modules/moocfi-quizzes;
24 | npm install --prod;
25 | cd ../..;
26 |
--------------------------------------------------------------------------------