├── .env
├── .eslintignore
├── .eslintrc.cjs
├── .github
├── dependabot.yml
└── workflows
│ ├── lint.yml
│ ├── nodejs.yml
│ └── tests.yml
├── .gitignore
├── .grenrc
├── .husky
└── pre-commit
├── .nvmrc
├── .prettierrc.json
├── .stylelintignore
├── .stylelintrc.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── index.html
├── netlify.toml
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── public
├── 404.html
├── img
│ ├── icons
│ │ ├── apple.png
│ │ ├── cloud.png
│ │ ├── earth.png
│ │ ├── heart.png
│ │ ├── hotdog.png
│ │ ├── icecream.png
│ │ ├── lightbulb.png
│ │ ├── orange.png
│ │ └── pear.png
│ ├── sketch-thumbnails
│ │ ├── Ant.svg
│ │ ├── Badger.svg
│ │ ├── Bear.svg
│ │ ├── Beaver.svg
│ │ ├── Bird.svg
│ │ ├── Bug.svg
│ │ ├── Bull.svg
│ │ ├── Bumblebee.svg
│ │ ├── Butterfly.svg
│ │ ├── Cat.svg
│ │ ├── Caterpillar.svg
│ │ ├── Chicken.svg
│ │ ├── Clown Fish.svg
│ │ ├── Corgi.svg
│ │ ├── Cow.svg
│ │ ├── Crab.svg
│ │ ├── Deer.svg
│ │ ├── Dinosaur.svg
│ │ ├── Dog.svg
│ │ ├── Dolphin.svg
│ │ ├── Dragonfly.svg
│ │ ├── Duck.svg
│ │ ├── Elephant.svg
│ │ ├── Falcon.svg
│ │ ├── Fish.svg
│ │ ├── Fly.svg
│ │ ├── Frog.svg
│ │ ├── Giraffe.svg
│ │ ├── Gorilla.svg
│ │ ├── Grasshopper.svg
│ │ ├── Horse.svg
│ │ ├── Hummingbird.svg
│ │ ├── Insect.svg
│ │ ├── Kangaroo.svg
│ │ ├── Kiwi Bird.svg
│ │ ├── Ladybird.svg
│ │ ├── Leopard.svg
│ │ ├── Lion.svg
│ │ ├── Llama.svg
│ │ ├── Mite.svg
│ │ ├── Mosquito.svg
│ │ ├── Octopus.svg
│ │ ├── Panda.svg
│ │ ├── Pig With Lipstick.svg
│ │ ├── Pig.svg
│ │ ├── Prawn.svg
│ │ ├── Puffin Bird.svg
│ │ ├── Rabbit.svg
│ │ ├── Rhinoceros.svg
│ │ ├── Seahorse.svg
│ │ ├── Shark.svg
│ │ ├── Sheep.svg
│ │ ├── Snail.svg
│ │ ├── Spider.svg
│ │ ├── Starfish.svg
│ │ ├── Stork.svg
│ │ ├── Turtle.svg
│ │ ├── Unicorn.svg
│ │ ├── Whale.svg
│ │ └── Wolf.svg
│ └── triangle.png
└── tla-logo.png
├── src
├── actions
│ ├── classesActions.js
│ ├── outputActions.js
│ ├── programsActions.js
│ ├── uiActions.js
│ └── userDataActions.js
├── components
│ ├── Class
│ │ ├── components
│ │ │ ├── ClassInfoBox.tsx
│ │ │ ├── ClassSketchList.jsx
│ │ │ └── StudentListEntry.jsx
│ │ ├── containers
│ │ │ ├── ClassPageContainer.js
│ │ │ └── CreateSketchModalContainer.js
│ │ └── index.jsx
│ ├── Classes
│ │ ├── components
│ │ │ ├── ClassBox.tsx
│ │ │ ├── ConfirmLeaveModal.jsx
│ │ │ ├── CreateClassModal.jsx
│ │ │ └── JoinClassModal.jsx
│ │ ├── containers
│ │ │ ├── ClassesContainer.js
│ │ │ ├── ConfirmLeaveModalContainer.js
│ │ │ ├── CreateClassModalContainer.js
│ │ │ └── JoinClassModalContainer.js
│ │ └── index.jsx
│ ├── EditorAndOutput
│ │ └── EditorAndOutput.jsx
│ ├── Error.tsx
│ ├── Login.jsx
│ ├── Login
│ │ ├── CreateUserForm.jsx
│ │ ├── LoginForm.tsx
│ │ └── LoginInput.jsx
│ ├── Main.jsx
│ ├── Output
│ │ ├── Output.jsx
│ │ └── OutputContainer.js
│ ├── PageNotFound.jsx
│ ├── Sketches
│ │ ├── components
│ │ │ ├── ConfirmDeleteModal.jsx
│ │ │ ├── CreateSketchModal.tsx
│ │ │ └── EditSketchModal.jsx
│ │ ├── constants
│ │ │ └── index.js
│ │ ├── containers
│ │ │ ├── ConfirmDeleteModalContainer.js
│ │ │ ├── CreateSketchModalContainer.js
│ │ │ ├── EditSketchModalContainer.js
│ │ │ └── SketchesContainer.js
│ │ └── index.jsx
│ ├── TextEditor
│ │ ├── components
│ │ │ ├── EditorRadio.jsx
│ │ │ ├── ShareSketchModal.jsx
│ │ │ └── TextEditor.tsx
│ │ └── containers
│ │ │ ├── DropdownButtonContainer.js
│ │ │ └── TextEditorContainer.js
│ ├── ViewOnly.jsx
│ ├── app.jsx
│ ├── common
│ │ ├── DropdownButton.jsx
│ │ ├── Footer.tsx
│ │ ├── ImageSelector.jsx
│ │ ├── LoadingPage.tsx
│ │ ├── OpenPanelButton.tsx
│ │ ├── ProfilePanel.jsx
│ │ ├── Radio.tsx
│ │ ├── SketchBox.tsx
│ │ ├── Switch.tsx
│ │ ├── ViewportAwareButton.tsx
│ │ └── containers
│ │ │ ├── OpenPanelButtonContainer.js
│ │ │ └── ProfilePanelContainer.js
│ └── containers
│ │ ├── AppContainer.js
│ │ ├── LoginContainer.js
│ │ ├── MainContainer.js
│ │ └── ViewOnlyContainer.js
├── constants
│ └── index.ts
├── firebase
│ └── index.js
├── history.js
├── img
│ ├── background1.svg
│ ├── blueguy.png
│ ├── login1.svg
│ ├── login2.svg
│ ├── login3.svg
│ ├── login4.svg
│ ├── login5.svg
│ ├── tla-logo-green.svg
│ └── tla-logo.svg
├── index.tsx
├── lib
│ ├── cookies.ts
│ ├── fetch.ts
│ ├── sketch.ts
│ └── validate.ts
├── react-app-env.d.ts
├── reducers
│ ├── classesReducer.js
│ ├── index.ts
│ ├── outputReducer.js
│ ├── programsReducer.js
│ ├── uiReducer.js
│ └── userDataReducer.js
├── registerServiceWorker.js
├── setupTests.js
├── store.js
├── styles
│ ├── ClassBox.scss
│ ├── ClassPage.scss
│ ├── Classes.scss
│ ├── CustomCM.scss
│ ├── Editor.scss
│ ├── Footer.scss
│ ├── ImageSelector.scss
│ ├── Loading.scss
│ ├── Login.scss
│ ├── Main.scss
│ ├── Modals.scss
│ ├── Output.scss
│ ├── Page.scss
│ ├── Panel.scss
│ ├── Radio.scss
│ ├── Resizer.scss
│ ├── SketchBox.scss
│ ├── Sketches.scss
│ ├── SketchesModal.scss
│ ├── Switch.scss
│ ├── app.scss
│ ├── global.scss
│ ├── triangle.png
│ └── variables.scss
└── util
│ ├── classes.js
│ └── languages
│ ├── CodeDownloader.js
│ ├── JsSourceDocLoggingScript.js
│ ├── Processing.js
│ ├── Python.js
│ ├── React.js
│ └── languages.js
├── tsconfig.json
└── vite.config.ts
/.env:
--------------------------------------------------------------------------------
1 | PORT=8080
2 | DISABLE_ESLINT_PLUGIN=true
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # vite config
2 | vite.config.ts
3 |
4 | # dependencies
5 | /node_modules
6 |
7 | # testing
8 | /coverage
9 |
10 | # production
11 | /build
12 | = ''
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "13:00"
8 | open-pull-requests-limit: 0
9 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | lint:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | node-version: [16.x]
17 | steps:
18 | - uses: actions/checkout@v2
19 | with:
20 | fetch-depth: 0
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v1
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | - run: npm install
26 | - name: Run ESlint
27 | run: |
28 | git fetch origin master
29 | npm run lint-js-changes
30 | - name: Run Stylelint
31 | run: |
32 | git fetch origin master
33 | npm run lint-css-changes
34 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | node-version: [20.x]
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v2
20 | - name: Use Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v1
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 | - run: npm install
25 | - run: npm run prod_build --if-present
26 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Run tests
2 | on: workflow_dispatch
3 |
4 | jobs:
5 | test-coverage:
6 | runs-on: ubuntu-latest
7 | strategy:
8 | matrix:
9 | node-version: [16.x]
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v2
13 | - name: Use Node.js ${{ matrix.node-version }}
14 | uses: actions/setup-node@v1
15 | with:
16 | node-version: ${{ matrix.node-version }}
17 | - run: npm install
18 | - name: Calc Coverage
19 | run: npm run test-coverage
20 | - name: Coveralls
21 | uses: coverallsapp/github-action@v1.1.2
22 | with:
23 | github-token: ${{ secrets.GITHUB_TOKEN }}
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 | .eslintcache
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | yarn.lock
24 |
--------------------------------------------------------------------------------
/.grenrc:
--------------------------------------------------------------------------------
1 | {
2 | "dataSource": "prs",
3 | "prefix": "Version ",
4 | "onlyMilestones": false,
5 | "groupBy": "label",
6 | "changelogFilename": "CHANGELOG.md"
7 | }
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | pnpm run pre-commit
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v16.12.0
2 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": [
3 | {
4 | "files": "*",
5 | "options": {
6 | "trailingComma": "all",
7 | "bracketSpacing": true,
8 | "printWidth": 100,
9 | "singleQuote": true
10 | }
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 |
4 | # testing
5 | /coverage
6 |
7 | # production
8 | /build
9 | /dist
10 |
11 | # ignore ts and js files
12 | *.ts
13 | *.tsx
14 | *.js
15 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-recommended", "stylelint-config-sass-guidelines", "stylelint-config-standard-scss"],
3 | "rules": {
4 | "color-named": null,
5 | "selector-max-id": null,
6 | "selector-no-qualifying-type": null,
7 | "property-no-vendor-prefix": null,
8 |
9 | "scss/at-import-partial-extension-blacklist": null,
10 | "declaration-property-value-disallowed-list": null,
11 | "selector-class-pattern": null
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 UCLA Association for Computing Machinery
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 | Teach LA Editor
20 |
21 |
50 |
51 |
52 |
53 |
56 |
57 |
58 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [context.production]
2 | command = "pnpm run prod_build"
3 |
4 | [context.deploy-preview]
5 | command = "pnpm run staging_build"
6 |
7 | [context.branch-deploy]
8 | command = "pnpm run staging_build"
9 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Teach LA | 404: Redirecting You...
6 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/public/img/icons/apple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/img/icons/apple.png
--------------------------------------------------------------------------------
/public/img/icons/cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/img/icons/cloud.png
--------------------------------------------------------------------------------
/public/img/icons/earth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/img/icons/earth.png
--------------------------------------------------------------------------------
/public/img/icons/heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/img/icons/heart.png
--------------------------------------------------------------------------------
/public/img/icons/hotdog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/img/icons/hotdog.png
--------------------------------------------------------------------------------
/public/img/icons/icecream.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/img/icons/icecream.png
--------------------------------------------------------------------------------
/public/img/icons/lightbulb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/img/icons/lightbulb.png
--------------------------------------------------------------------------------
/public/img/icons/orange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/img/icons/orange.png
--------------------------------------------------------------------------------
/public/img/icons/pear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/img/icons/pear.png
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Badger.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Bear.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Beaver.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Bird.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Bug.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Cat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Caterpillar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Chicken.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Corgi.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Crab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Dog.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Dolphin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Dragonfly.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Duck.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Elephant.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Frog.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Giraffe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Grasshopper.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Horse.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Insect.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Kangaroo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Ladybird.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Lion.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Mite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Panda.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Pig With Lipstick.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Pig.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Puffin Bird.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Rabbit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Rhinoceros.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Shark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Spider.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Starfish.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Stork.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Turtle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Unicorn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Whale.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/sketch-thumbnails/Wolf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/img/triangle.png
--------------------------------------------------------------------------------
/public/tla-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/public/tla-logo.png
--------------------------------------------------------------------------------
/src/actions/classesActions.js:
--------------------------------------------------------------------------------
1 | export const ADD_STUDENT_CLASS = 'ADD_STUDENT_CLASS';
2 | export function addStudentClass(cid, data) {
3 | return { type: ADD_STUDENT_CLASS, cid, data };
4 | }
5 |
6 | export const REMOVE_STUDENT_CLASS = 'REMOVE_STUDENT_CLASS';
7 | export function removeStudentClass(cid) {
8 | return { type: REMOVE_STUDENT_CLASS, cid };
9 | }
10 |
11 | export const LOAD_STUDENT_CLASSES = 'LOAD_STUDENT_CLASSES';
12 | export function loadStudentClasses(classes) {
13 | return { type: LOAD_STUDENT_CLASSES, classes };
14 | }
15 |
16 | export const CLEAR_STUDENT_CLASSES = 'CLEAR_STUDENT_CLASSES';
17 | export function clearStudentClasses() {
18 | return { type: CLEAR_STUDENT_CLASSES };
19 | }
20 |
21 | export const ADD_INSTR_CLASS = 'ADD_INSTR_CLASS';
22 | export function addInstrClass(cid, data) {
23 | return { type: ADD_INSTR_CLASS, cid, data };
24 | }
25 |
26 | export const REMOVE_INSTR_CLASS = 'REMOVE_INSTR_CLASS';
27 | export function removeInstrClass(cid) {
28 | return { type: REMOVE_INSTR_CLASS, cid };
29 | }
30 |
31 | export const LOAD_INSTR_CLASSES = 'LOAD_INSTR_CLASSES';
32 | export function loadInstrClasses(classes) {
33 | return { type: LOAD_INSTR_CLASSES, classes };
34 | }
35 |
36 | export const CLEAR_INSTR_CLASSES = 'CLEAR_INSTR_CLASSES';
37 | export function clearInstrClasses() {
38 | return { type: CLEAR_INSTR_CLASSES };
39 | }
40 |
41 | export const CLEAR_CLASSES = 'CLEAR_CLASSES';
42 | export function clearClasses() {
43 | return { type: CLEAR_CLASSES };
44 | }
45 |
--------------------------------------------------------------------------------
/src/actions/outputActions.js:
--------------------------------------------------------------------------------
1 | export const CLEAR_OUTPUT = 'CLEAR_OUTPUT';
2 | export function clearOutput() {
3 | return { type: CLEAR_OUTPUT };
4 | }
5 |
6 | export const SET_RUN_RESULT = 'SET_RUN_RESULT';
7 | export function setRunResult(value) {
8 | return { type: SET_RUN_RESULT, value };
9 | }
10 |
11 | export const SET_OUTPUT_LANGUAGE = 'SET_OUTPUT_LANGAUGE';
12 | export function setOutputLanguage(value) {
13 | return { type: SET_OUTPUT_LANGUAGE, value };
14 | }
15 |
16 | export const SET_OUTPUT = 'SET_OUTPUT';
17 | export function setOutput({ output }) {
18 | // if output is not an object
19 | if (!output) {
20 | return { type: 'IGNORE' };
21 | }
22 | return { type: SET_OUTPUT, runResult: output.code, language: output.language };
23 | }
24 |
--------------------------------------------------------------------------------
/src/actions/programsActions.js:
--------------------------------------------------------------------------------
1 | export const SET_PROGRAM_CODE = 'SET_PROGRAM_CODE';
2 | export function setProgramCode(program, value) {
3 | return { type: SET_PROGRAM_CODE, program, value };
4 | }
5 |
6 | export const SET_PROGRAM_LANGUAGE = 'SET_PROGRAM_LANGUAGE';
7 | export function setProgramLanguage(program, value) {
8 | return { type: SET_PROGRAM_LANGUAGE, program, value };
9 | }
10 |
11 | export const SET_PROGRAM_NAME = 'SET_PROGRAM_NAME';
12 | export function setProgramName(program, value) {
13 | return { type: SET_PROGRAM_NAME, program, value };
14 | }
15 |
16 | export const SET_PROGRAM_THUMBNAIL = 'SET_PROGRAM_THUMBNAIL';
17 | export function setProgramThumbnail(program, value) {
18 | return { type: SET_PROGRAM_THUMBNAIL, program, value };
19 | }
20 |
21 | export const DELETE_PROGRAM = 'DELETE_PROGRAM';
22 | export function deleteProgram(program) {
23 | return { type: DELETE_PROGRAM, program };
24 | }
25 |
26 | export const LOAD_PROGRAMS = 'LOAD_PROGRAMS';
27 | export function loadPrograms(programs) {
28 | return { type: LOAD_PROGRAMS, programs };
29 | }
30 |
31 | export const CLEAR_PROGRAMS = 'CLEAR_PROGRAMS';
32 | export function clearPrograms() {
33 | return { type: CLEAR_PROGRAMS };
34 | }
35 |
36 | export const SET_PROGRAM_DIRTY = 'SET_PROGRAM_DIRTY';
37 | export function setProgramDirty(program, value) {
38 | return { type: SET_PROGRAM_DIRTY, program, value };
39 | }
40 |
41 | export const ADD_PROGRAM = 'ADD_PROGRAM';
42 | export function addProgram(program, data) {
43 | return { type: ADD_PROGRAM, program, data };
44 | }
45 |
--------------------------------------------------------------------------------
/src/actions/uiActions.js:
--------------------------------------------------------------------------------
1 | export const SCREEN_RESIZE = 'SCREEN_RESIZE';
2 | export function screenResize(width, height) {
3 | return { type: SCREEN_RESIZE, width, height };
4 | }
5 |
6 | export const TOGGLE_PANEL = 'TOGGLE_PANEL';
7 | export function togglePanel() {
8 | return { type: TOGGLE_PANEL };
9 | }
10 |
11 | export const SET_PANEL = 'SET_PANEL';
12 | export function setPanel(value) {
13 | return { type: SET_PANEL, value };
14 | }
15 |
16 | export const SET_THEME = 'SET_THEME';
17 | export function setTheme(theme) {
18 | return { type: SET_THEME, theme };
19 | }
20 |
21 | export const SET_CLASSES_LOADED = 'SET_CLASSES_LOADED';
22 | export function setClassesLoaded(loaded) {
23 | return { type: SET_CLASSES_LOADED, value: loaded };
24 | }
25 |
26 | export const SET_ON_INSTR_VIEW = 'SET_ON_INSTR_VIEW';
27 | export function setOnInstrView(value) {
28 | return { type: SET_ON_INSTR_VIEW, value };
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/Class/components/ClassInfoBox.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../../../styles/ClassPage.scss';
3 |
4 | interface ClassInfoBoxProps {
5 | title: string;
6 | children: React.ReactNode;
7 | }
8 |
9 | const ClassInfoBox = ({
10 | title,
11 | children,
12 | } : ClassInfoBoxProps) => {
13 | return (
14 |
15 |
{title}
16 |
{children}
17 |
18 | );
19 | };
20 |
21 | export default ClassInfoBox;
22 |
--------------------------------------------------------------------------------
/src/components/Class/components/ClassSketchList.jsx:
--------------------------------------------------------------------------------
1 | import { faPlus } from '@fortawesome/free-solid-svg-icons';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 |
4 | import { ThumbnailArray } from '../../../constants';
5 | import CodeDownloader from '../../../util/languages/CodeDownloader';
6 | import SketchBox from '../../common/SketchBox';
7 |
8 | import '../../../styles/SketchBox.scss';
9 |
10 | const SKETCHES_ROW_PADDING = 100;
11 | const SKETCH_WIDTH = 220;
12 |
13 | function ClassSketchList({
14 | calculatedWidth, isInstr, programData, setCreateSketchModalOpen,
15 | }) {
16 | const newList = programData?.concat([]) || [];
17 | newList.sort((a, b) => {
18 | if (a.name < b.name) return -1;
19 | if (a.name === b.name) return 0;
20 | return 1;
21 | });
22 |
23 | const getThumbnailSrc = (val) => {
24 | if (val === undefined || val === '' || val >= ThumbnailArray.length || val < 0) {
25 | return ThumbnailArray[0];
26 | }
27 | return ThumbnailArray[val];
28 | };
29 |
30 | const sketchList = newList.map(({
31 | uid, name, language, thumbnail, code,
32 | }) => (
33 | {
39 | CodeDownloader.download(name, language, code);
40 | }}
41 | pathname={isInstr ? `/editor/${uid}` : `/p/${uid}`}
42 | />
43 | ));
44 |
45 | // Button for instructors to add a sketch to the class.
46 | if (isInstr) {
47 | sketchList.push(
48 | ,
61 | );
62 | }
63 |
64 | // TODO: This should be a flexbox, instead of this
65 | const numSketchesPerRow = Math.floor((calculatedWidth - SKETCHES_ROW_PADDING) / SKETCH_WIDTH);
66 | const rows = [];
67 | const originalLength = sketchList.length;
68 | for (let i = 0; i < originalLength / numSketchesPerRow; i++) {
69 | rows.push(
70 |
71 | {sketchList.splice(0, numSketchesPerRow)}
72 |
,
73 | );
74 | }
75 | if (rows.length === 0) return 'No sketches found';
76 | return {rows}
;
77 | }
78 |
79 | export default ClassSketchList;
80 |
--------------------------------------------------------------------------------
/src/components/Class/components/StudentListEntry.jsx:
--------------------------------------------------------------------------------
1 | import '../../../styles/ClassBox.scss';
2 |
3 | const StudentListEntry = function (props) {
4 | const { name } = props;
5 | return (
6 |
7 |
8 |
{name}
9 |
{name}
10 |
11 |
12 | );
13 | };
14 |
15 | export default StudentListEntry;
16 |
--------------------------------------------------------------------------------
/src/components/Class/containers/ClassPageContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { addInstrClass, addStudentClass } from '../../../actions/classesActions';
3 | import { addProgram } from '../../../actions/programsActions';
4 | import { togglePanel } from '../../../actions/uiActions';
5 | import { setMostRecentProgram } from '../../../actions/userDataActions';
6 | import { OPEN_PANEL_LEFT, CLOSED_PANEL_LEFT, PANEL_SIZE } from '../../../constants';
7 | import ClassPage from '../index';
8 |
9 | const mapStateToProps = (state) => {
10 | const left = (state.ui.panelOpen ? OPEN_PANEL_LEFT : CLOSED_PANEL_LEFT) + PANEL_SIZE;
11 | const calculatedWidth = state.ui.screenWidth - (left || 0);
12 | const cid = state.userData.currentClass;
13 | const blankClass = {
14 | name: '',
15 | creator: '',
16 | thumbnail: 0,
17 | programData: null,
18 | programs: null,
19 | wid: '',
20 | userData: {},
21 | instructors: [],
22 | members: null,
23 | cid: '',
24 | };
25 |
26 | return {
27 | calculatedWidth,
28 | left,
29 | screenHeight: state.ui.screenHeight,
30 | panelOpen: state.ui.panelOpen,
31 | uid: state.userData.uid,
32 | cid,
33 | classData:
34 | state.classes.getIn(['instrClasses', cid])?.toJS()
35 | || state.classes.getIn(['studClasses', cid])?.toJS()
36 | || blankClass,
37 | };
38 | };
39 |
40 | const mapDispatchToProps = (dispatch) => ({
41 | addInstrClass: (cid, data) => dispatch(addInstrClass(cid, data)),
42 | addStudentClass: (cid, data) => dispatch(addStudentClass(cid, data)),
43 | addProgram: (program, value) => dispatch(addProgram(program, value)),
44 | setMostRecentProgram: (value) => dispatch(setMostRecentProgram(value)),
45 | togglePanel: () => dispatch(togglePanel()),
46 | });
47 |
48 | const ClassPageContainer = connect(mapStateToProps, mapDispatchToProps)(ClassPage);
49 |
50 | export default ClassPageContainer;
51 |
--------------------------------------------------------------------------------
/src/components/Class/containers/CreateSketchModalContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { addProgram } from '../../../actions/programsActions';
3 | import { setMostRecentProgram } from '../../../actions/userDataActions';
4 | import CreateSketchModal from '../../Sketches/components/CreateSketchModal';
5 |
6 | const mapStateToProps = (state) => ({
7 | uid: state.userData.uid,
8 | });
9 |
10 | const mapDispatchToProps = (dispatch) => ({
11 | addProgram: (program, data) => {
12 | dispatch(addProgram(program, data));
13 | },
14 | setMostRecentProgram: (value) => dispatch(setMostRecentProgram(value)),
15 | });
16 |
17 | const CreateSketchModalContainer = connect(mapStateToProps, mapDispatchToProps)(CreateSketchModal);
18 |
19 | export default CreateSketchModalContainer;
20 |
--------------------------------------------------------------------------------
/src/components/Classes/components/ClassBox.tsx:
--------------------------------------------------------------------------------
1 | import { faTrashAlt } from '@fortawesome/free-solid-svg-icons';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { Link } from 'react-router-dom';
4 | import '../../../styles/ClassBox.scss';
5 |
6 | interface ClassBoxProps {
7 | deleteFunc: () => void;
8 | img: string;
9 | instructorString: string;
10 | name: string;
11 | redirFunc: () => void;
12 | showLeaveButton: JSX.Element;
13 | }
14 |
15 | const ClassBox = function ({
16 | deleteFunc, img, instructorString, name, redirFunc, showLeaveButton,
17 | }: ClassBoxProps) {
18 | const leaveButton = showLeaveButton ? (
19 |
26 |
27 |
28 | ) : (
29 | ''
30 | );
31 |
32 | return (
33 |
34 |
35 |

40 |
41 |
{name}
42 |
{instructorString}
43 |
44 |
45 | {leaveButton}
46 |
47 | );
48 | };
49 |
50 | export default ClassBox;
51 |
--------------------------------------------------------------------------------
/src/components/Classes/components/ConfirmLeaveModal.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import ReactModal from 'react-modal';
3 | import {
4 | Container, Row, Col, Button,
5 | } from 'reactstrap';
6 | import * as fetch from '../../../lib/fetch';
7 |
8 | const ConfirmLeaveModal = function (props) {
9 | const {
10 | isOpen, uid, cid, removeStudentClass, className, inClass, unsetClass,
11 | } = props;
12 |
13 | const [error, setError] = useState('');
14 |
15 | const closeModal = () => {
16 | const { onClose } = props;
17 | if (onClose && {}.toString.call(onClose) === '[object Function]') {
18 | onClose();
19 | }
20 | };
21 |
22 | const onLeaveSubmit = () => {
23 | const data = {
24 | uid,
25 | cid,
26 | };
27 | try {
28 | // Note: leaveClass doesn't currently return anything.
29 | fetch
30 | .leaveClass(data)
31 | .then((res) => {
32 | if (!res.ok) {
33 | setError(res.error || 'Failed to leave the class, please try again later');
34 | return;
35 | }
36 | removeStudentClass(cid);
37 | if (inClass) {
38 | unsetClass();
39 | } else {
40 | closeModal();
41 | }
42 | })
43 | .catch((err) => {
44 | setError('Failed to leave the class, please try again later');
45 | console.error(err);
46 | });
47 | } catch (err) {
48 | console.error(err);
49 | }
50 |
51 | // Testing stuff (do this instead of the try-catch block):
52 | // this.props.removeStudentClass(this.props.cid);
53 | // this.props.inClass ? this.props.unsetClass() : this.closeModal();
54 | // end of test stuff
55 |
56 | setError('');
57 | };
58 |
59 | return (
60 |
66 |
67 |
68 | Are you sure you want to permanently leave the class "
69 | {className}
70 | "?
71 |
72 |
73 | {error ||
}
74 |
75 |
76 |
79 |
80 |
81 |
84 |
85 |
86 |
87 |
88 | );
89 | };
90 |
91 | export default ConfirmLeaveModal;
92 |
--------------------------------------------------------------------------------
/src/components/Classes/containers/ClassesContainer.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'immutable';
2 | import { connect } from 'react-redux';
3 | import { loadInstrClasses, loadStudentClasses } from '../../../actions/classesActions';
4 | import { togglePanel, setClassesLoaded, setOnInstrView } from '../../../actions/uiActions';
5 | import { setCurrentClass } from '../../../actions/userDataActions';
6 | import { OPEN_PANEL_LEFT, CLOSED_PANEL_LEFT, PANEL_SIZE } from '../../../constants';
7 | import Classes from '../index';
8 |
9 | const mapStateToProps = (state) => {
10 | // TODO: Look over this part (instrClasses and studentClasses)
11 | const instrClasses = state.classes
12 | .get('instrClasses')
13 | .keySeq()
14 | .map((id) => {
15 | const temp = state.classes.get('instrClasses').get(id, Immutable.Map()).toJS();
16 | temp.cid = id;
17 | return temp;
18 | });
19 | const studentClasses = state.classes
20 | .get('studClasses')
21 | .keySeq()
22 | .map((id) => {
23 | const temp = state.classes.get('studClasses').get(id, Immutable.Map()).toJS();
24 | temp.cid = id;
25 | return temp;
26 | });
27 |
28 | const left = (state.ui.panelOpen ? OPEN_PANEL_LEFT : CLOSED_PANEL_LEFT) + PANEL_SIZE;
29 | const calculatedWidth = state.ui.screenWidth - (left || 0);
30 |
31 | return {
32 | studentClasses,
33 | instrClasses,
34 | calculatedWidth,
35 | left,
36 | screenHeight: state.ui.screenHeight,
37 | panelOpen: state.ui.panelOpen,
38 | classesLoaded: state.ui.classesLoaded,
39 | onInstrView: state.ui.onInstrView,
40 | classList: state.userData.classes,
41 | uid: state.userData.uid,
42 | username: state.userData.displayName,
43 | };
44 | };
45 |
46 | const mapDispatchToProps = (dispatch) => ({
47 | setCurrentClass: (value) => dispatch(setCurrentClass(value)),
48 | togglePanel: () => dispatch(togglePanel()),
49 | loadInstrClasses: (classes) => dispatch(loadInstrClasses(classes)),
50 | loadStudentClasses: (classes) => dispatch(loadStudentClasses(classes)),
51 | setClassesLoaded: (value) => dispatch(setClassesLoaded(value)),
52 | setOnInstrView: (value) => dispatch(setOnInstrView(value)),
53 | });
54 |
55 | const ClassesContainer = connect(mapStateToProps, mapDispatchToProps)(Classes);
56 |
57 | export default ClassesContainer;
58 |
--------------------------------------------------------------------------------
/src/components/Classes/containers/ConfirmLeaveModalContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { removeStudentClass } from '../../../actions/classesActions';
3 | import { setCurrentClass } from '../../../actions/userDataActions';
4 | import ConfirmLeaveModal from '../components/ConfirmLeaveModal';
5 |
6 | const mapStateToProps = (state) => ({
7 | uid: state.userData.uid,
8 | });
9 |
10 | const mapDispatchToProps = (dispatch) => ({
11 | removeStudentClass: (cid) => dispatch(removeStudentClass(cid)),
12 | unsetClass: () => dispatch(setCurrentClass('')),
13 | });
14 |
15 | const ConfirmLeaveModalContainer = connect(mapStateToProps, mapDispatchToProps)(ConfirmLeaveModal);
16 |
17 | export default ConfirmLeaveModalContainer;
18 |
--------------------------------------------------------------------------------
/src/components/Classes/containers/CreateClassModalContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { addInstrClass } from '../../../actions/classesActions';
3 | import { setCurrentClass } from '../../../actions/userDataActions';
4 | import CreateClassModal from '../components/CreateClassModal';
5 |
6 | const mapStateToProps = (state) => ({
7 | uid: state.userData.uid,
8 | });
9 |
10 | const mapDispatchToProps = (dispatch) => ({
11 | addInstrClass: (cid, data) => dispatch(addInstrClass(cid, data)),
12 | setCurrentClass: (cid) => dispatch(setCurrentClass(cid)),
13 | });
14 |
15 | const CreateClassModalContainer = connect(mapStateToProps, mapDispatchToProps)(CreateClassModal);
16 |
17 | export default CreateClassModalContainer;
18 |
--------------------------------------------------------------------------------
/src/components/Classes/containers/JoinClassModalContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { addStudentClass } from '../../../actions/classesActions';
3 | import JoinClassModal from '../components/JoinClassModal';
4 |
5 | const mapStateToProps = (state) => ({
6 | uid: state.userData.uid,
7 | });
8 |
9 | const mapDispatchToProps = (dispatch) => ({
10 | addStudentClass: (cid, data) => dispatch(addStudentClass(cid, data)),
11 | });
12 |
13 | const JoinClassModalContainer = connect(mapStateToProps, mapDispatchToProps)(JoinClassModal);
14 |
15 | export default JoinClassModalContainer;
16 |
--------------------------------------------------------------------------------
/src/components/Error.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 | import { Button } from 'reactstrap';
3 |
4 | import { GH_REPO_NAME } from '../constants';
5 | import { signOut } from '../firebase';
6 | import * as cookies from '../lib/cookies';
7 | import Footer from './common/Footer';
8 | import '../styles/Page.scss';
9 |
10 | interface ErrorProps {
11 | errorMsg: string;
12 | isValidUser: boolean;
13 | returnTo: string;
14 | };
15 |
16 | function Error({ errorMsg, returnTo = '/', isValidUser }: ErrorProps) {
17 | return (
18 |
19 |
20 |
Uh oh, something went wrong!
21 |
22 | Error:
23 | {errorMsg}
24 |
25 |
26 |
29 |
30 |
31 |
34 |
35 |
36 | {isValidUser && (
37 |
40 | )}
41 |
42 |
43 |
44 | );
45 | }
46 |
47 | export default Error;
48 |
--------------------------------------------------------------------------------
/src/components/Login/LoginInput.jsx:
--------------------------------------------------------------------------------
1 | import '../../styles/Login.scss';
2 |
3 | /** -------Props-------
4 | * type: string, if password, hides the input with dots; also used as the header for the input
5 | * waiting: boolean that disables the input if true
6 | * data: value inside the input
7 | * onChange: function to be called when input changes
8 | */
9 |
10 | const LoginInput = function ({
11 | type, waiting, data, onChange,
12 | }) {
13 | return (
14 |
15 |
{type}
16 |
onChange(e.target.value)}
24 | />
25 |
26 |
27 | );
28 | };
29 |
30 | export default LoginInput;
31 |
--------------------------------------------------------------------------------
/src/components/Output/OutputContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { getLanguageData } from '../../util/languages/languages';
3 | import Output from './Output';
4 |
5 | const mapStateToProps = (state) => {
6 | const { mostRecentProgram } = state.userData;
7 | return {
8 | mostRecentProgram,
9 | runResult: state.programs.getIn([mostRecentProgram, 'code']),
10 | language: getLanguageData(state.programs.getIn([mostRecentProgram, 'language'])),
11 | screenHeight: state.ui.screenHeight,
12 | screenWidth: state.ui.screenWidth, // probably will need this
13 | };
14 | };
15 |
16 | const mapDispatchToProps = () => ({
17 | clearOutput: () => {},
18 | });
19 |
20 | const OutputContainer = connect(mapStateToProps, mapDispatchToProps)(Output);
21 |
22 | export default OutputContainer;
23 |
--------------------------------------------------------------------------------
/src/components/PageNotFound.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 | import { Button } from 'reactstrap';
3 | import '../styles/Page.scss';
4 | import { GH_REPO_NAME } from '../constants';
5 | import * as cookies from '../lib/cookies';
6 | import Footer from './common/Footer';
7 |
8 | const PageNotFound = function () {
9 | return (
10 |
11 |
12 |
Error: 404
13 |
Uh oh, page not found!
14 |
15 |
16 |
19 |
20 |
21 |
22 | Getting this problem a lot? Let us know
23 | {' '}
24 |
25 | on GitHub
26 |
27 | .
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default PageNotFound;
36 |
--------------------------------------------------------------------------------
/src/components/Sketches/components/ConfirmDeleteModal.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import ReactModal from 'react-modal';
3 | import {
4 | Container, Row, Col, Button,
5 | } from 'reactstrap';
6 | import * as fetch from '../../../lib/fetch';
7 |
8 | const ConfirmDeleteModal = (props) => {
9 | const {
10 | onClose, isOpen, sketchName, uid, sketchKey, deleteProgram,
11 | } = props;
12 |
13 | const [_spinner, setSpinner] = useState(true);
14 | const [_error, setError] = useState('');
15 |
16 | const closeModal = () => {
17 | if (onClose && {}.toString.call(onClose) === '[object Function]') {
18 | onClose();
19 | }
20 | };
21 |
22 | const onDeleteSubmit = () => {
23 | const data = {
24 | uid,
25 | docID: sketchKey,
26 | name: sketchKey,
27 | };
28 | try {
29 | fetch
30 | .deleteSketch(data)
31 | .then((res) => {
32 | if (res.ok) {
33 | deleteProgram(sketchKey);
34 | closeModal();
35 | } else {
36 | setSpinner(false);
37 | setError(res.text() || 'Failed to delete sketch, please try again later');
38 | }
39 | })
40 | .catch((err) => {
41 | setSpinner(false);
42 | setError('Failed to create sketch, please try again later');
43 | console.error(err);
44 | });
45 | } catch (err) {
46 | console.error(err);
47 | }
48 | setSpinner(true);
49 | setError('');
50 | };
51 |
52 | return (
53 |
59 |
60 |
61 | Are you sure you want to delete "
62 | {sketchName}
63 | "?
64 |
65 |
66 |
67 |
68 |
71 |
72 |
73 |
76 |
77 |
78 |
79 |
80 | );
81 | };
82 |
83 | export default ConfirmDeleteModal;
84 |
--------------------------------------------------------------------------------
/src/components/Sketches/constants/index.js:
--------------------------------------------------------------------------------
1 | import { SUPPORTED_LANGUAGES } from '../../../util/languages/languages';
2 |
3 | export const LanguageDropdownValues = SUPPORTED_LANGUAGES.map(({ value, display }) => ({
4 | value,
5 | display,
6 | }));
7 | export const LanguageDropdownDefault = LanguageDropdownValues[0];
8 |
--------------------------------------------------------------------------------
/src/components/Sketches/containers/ConfirmDeleteModalContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { deleteProgram } from '../../../actions/programsActions';
3 | import ConfirmDeleteModal from '../components/ConfirmDeleteModal';
4 |
5 | const mapStateToProps = (state) => ({ uid: state.userData.uid });
6 |
7 | const mapDispatchToProps = (dispatch) => ({
8 | deleteProgram: (program, data) => dispatch(deleteProgram(program, data)),
9 | });
10 |
11 | const ConfirmDeleteModalContainer = connect(
12 | mapStateToProps,
13 | mapDispatchToProps,
14 | )(ConfirmDeleteModal);
15 |
16 | export default ConfirmDeleteModalContainer;
17 |
--------------------------------------------------------------------------------
/src/components/Sketches/containers/CreateSketchModalContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { addProgram } from '../../../actions/programsActions';
3 | import { setMostRecentProgram } from '../../../actions/userDataActions';
4 | import * as fetch from '../../../lib/fetch';
5 | import CreateSketchModal from '../components/CreateSketchModal';
6 |
7 | const mapStateToProps = (state) => ({
8 | uid: state.userData.uid,
9 | });
10 |
11 | const mapDispatchToProps = (dispatch) => ({
12 | addProgram: (program, data) => dispatch(addProgram(program, data)),
13 | setMostRecentProgram: (value, uid) => {
14 | try {
15 | fetch.updateUserData(uid, { mostRecentProgram: value }).catch((err) => {
16 | console.error(err);
17 | });
18 | } catch (err) {
19 | console.error(err);
20 | }
21 | dispatch(setMostRecentProgram(value));
22 | },
23 | });
24 |
25 | const CreateSketchModalContainer = connect(mapStateToProps, mapDispatchToProps)(CreateSketchModal);
26 |
27 | export default CreateSketchModalContainer;
28 |
--------------------------------------------------------------------------------
/src/components/Sketches/containers/EditSketchModalContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import {
3 | setProgramLanguage,
4 | setProgramName,
5 | setProgramThumbnail,
6 | } from '../../../actions/programsActions';
7 | import EditSketchModal from '../components/EditSketchModal';
8 |
9 | const mapStateToProps = (state) => ({
10 | uid: state.userData.uid,
11 | });
12 |
13 | const mapDispatchToProps = (dispatch) => ({
14 | setProgramLanguage: (program, value) => dispatch(setProgramLanguage(program, value)),
15 | setProgramName: (program, value) => dispatch(setProgramName(program, value)),
16 | setProgramThumbnail: (program, value) => dispatch(setProgramThumbnail(program, value)),
17 | });
18 |
19 | const EditSketchModalContainer = connect(
20 | mapStateToProps,
21 | mapDispatchToProps,
22 | )(EditSketchModal);
23 |
24 | export default EditSketchModalContainer;
25 |
--------------------------------------------------------------------------------
/src/components/Sketches/containers/SketchesContainer.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'immutable';
2 | import { connect } from 'react-redux';
3 | import { togglePanel } from '../../../actions/uiActions';
4 | import { setMostRecentProgram } from '../../../actions/userDataActions';
5 | import { OPEN_PANEL_LEFT, CLOSED_PANEL_LEFT, PANEL_SIZE } from '../../../constants';
6 | import * as fetch from '../../../lib/fetch';
7 | import { getLanguageData } from '../../../util/languages/languages';
8 | import Sketches from '../index';
9 |
10 | const mapStateToProps = (state) => {
11 | const { mostRecentProgram } = state.userData;
12 | const programs = state.programs.keySeq().map((id) => {
13 | const temp = state.programs.get(id, Immutable.Map()).toJS();
14 | temp.key = id;
15 | temp.language = getLanguageData(temp.language);
16 | return temp;
17 | });
18 |
19 | const left = (state.ui.panelOpen ? OPEN_PANEL_LEFT : CLOSED_PANEL_LEFT) + PANEL_SIZE;
20 | const calculatedWidth = state.ui.screenWidth - (left || 0);
21 |
22 | return {
23 | mostRecentProgram,
24 | programs,
25 | calculatedWidth,
26 | left,
27 | screenHeight: state.ui.screenHeight,
28 | panelOpen: state.ui.panelOpen,
29 | uid: state.userData.uid,
30 | };
31 | };
32 |
33 | const mapDispatchToProps = (dispatch) => ({
34 | setMostRecentProgram: (value, uid) => {
35 | try {
36 | fetch.updateUserData(uid, { mostRecentProgram: value }).catch((err) => {
37 | console.error(err);
38 | });
39 | } catch (err) {
40 | console.error(err);
41 | }
42 | dispatch(setMostRecentProgram(value));
43 | },
44 | togglePanel: () => dispatch(togglePanel()),
45 | });
46 |
47 | const SketchesContainer = connect(mapStateToProps, mapDispatchToProps)(Sketches);
48 |
49 | export default SketchesContainer;
50 |
--------------------------------------------------------------------------------
/src/components/TextEditor/components/EditorRadio.jsx:
--------------------------------------------------------------------------------
1 | import { CODE_AND_OUTPUT, CODE_ONLY, OUTPUT_ONLY } from '../../../constants';
2 | import Radio from '../../common/Radio';
3 |
4 | const EditorRadio = function (props) {
5 | let options = [];
6 | if (!props.isSmall) options.push({ display: 'Both', value: CODE_AND_OUTPUT });
7 | options = options.concat([
8 | { display: 'Code', value: CODE_ONLY },
9 | { display: 'Output', value: OUTPUT_ONLY },
10 | ]);
11 |
12 | return (
13 |
19 | );
20 | };
21 |
22 | export default EditorRadio;
23 |
--------------------------------------------------------------------------------
/src/components/TextEditor/components/ShareSketchModal.jsx:
--------------------------------------------------------------------------------
1 | import { faCopy } from '@fortawesome/free-solid-svg-icons';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { useState } from 'react';
4 | import ReactModal from 'react-modal';
5 |
6 | import { Button, Input, InputGroup } from 'reactstrap';
7 |
8 | import '../../../styles/Modals.scss';
9 |
10 | /**
11 | * ShareSketchModal is a full-screen modal that displays
12 | * the current program's view-only route, which the user can manually
13 | * copy or use the "Copy To Clipboard" Button
14 | * @param {Boolean} showModal modal render state
15 | * @param {Function} toggleModal function that toggles the modal's render state
16 | * @param {String} shareUrl the URL to display/copy to clipboard
17 | * showModal modal render state
18 | * toggleModal function that toggles the modal's render state
19 | * shareUrl the URL to display/copy to clipboard
20 | */
21 |
22 | function ShareSketchModal(props) {
23 | const { shareUrl, showModal, toggleModal } = props;
24 | const [copyStatus, setCopyStatus] = useState('Hit "Copy to Clipboard"!');
25 |
26 | const initiateCopy = () => {
27 | navigator.clipboard.writeText(shareUrl).then(
28 | () => {
29 | // success
30 | setCopyStatus('Successfully copied!');
31 | },
32 | () => {
33 | // failed
34 | setCopyStatus('Copy failed. If this keeps on happening, let us know!');
35 | },
36 | );
37 | };
38 |
39 | return (
40 |
47 | Share This File
48 |
49 |
50 |
55 |
56 |
57 | {copyStatus}
58 |
59 | );
60 | }
61 |
62 | export default ShareSketchModal;
63 |
--------------------------------------------------------------------------------
/src/components/TextEditor/containers/DropdownButtonContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { setMostRecentProgram } from '../../../actions/userDataActions';
3 | import * as fetch from '../../../lib/fetch';
4 | import { getLanguageData } from '../../../util/languages/languages';
5 | import DropdownButton from '../../common/DropdownButton';
6 |
7 | const mapStateToProps = (state) => {
8 | const { mostRecentProgram } = state.userData;
9 |
10 | const mostRecentLanguage = getLanguageData(
11 | state.programs.getIn([mostRecentProgram, 'language'], 'python'),
12 | );
13 |
14 | const displayValue = ` ${state.programs.getIn([mostRecentProgram, 'name'], mostRecentProgram)}`;
15 |
16 | const programStateValues = state.programs.keySeq().map((id) => ({
17 | display: state.programs.getIn([id, 'name'], id),
18 | value: id,
19 | icon: getLanguageData(state.programs.getIn([id, 'language'], 'python')).icon,
20 | }));
21 |
22 | const dirty = state.programs.getIn([mostRecentProgram, 'dirty'], false);
23 |
24 | const displayClass = 'editor';
25 |
26 | const toggleProps = {
27 | className: 'btn-language-dropdown',
28 | color: '',
29 | size: '',
30 | };
31 |
32 | const { icon } = mostRecentLanguage;
33 |
34 | return {
35 | displayValue,
36 | displayClass,
37 | dirty,
38 | DropdownItems: programStateValues,
39 | icon,
40 | toggleProps,
41 | uid: state.userData.uid,
42 | };
43 | };
44 |
45 | const mapDispatchToProps = (dispatch) => ({
46 | onSelect: ({
47 | _display, value, _dirty, uid,
48 | }) => {
49 | // TODO: Fix this dirty check
50 | // if (dirty) {
51 | // result = window.confirm('Are you sure you want to change programs? You have unsaved changes');
52 | // }
53 | try {
54 | fetch.updateUserData(uid, { mostRecentProgram: value }).catch((err) => {
55 | console.error(err);
56 | });
57 | } catch (err) {
58 | console.error(err);
59 | }
60 | dispatch(setMostRecentProgram(value));
61 | },
62 | });
63 |
64 | const DropdownButtonContainer = connect(mapStateToProps, mapDispatchToProps)(DropdownButton);
65 |
66 | export default DropdownButtonContainer;
67 |
--------------------------------------------------------------------------------
/src/components/TextEditor/containers/TextEditorContainer.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'immutable';
2 | import { connect } from 'react-redux';
3 | import { addProgram, setProgramCode, setProgramDirty } from '../../../actions/programsActions';
4 |
5 | import { setMostRecentProgram } from '../../../actions/userDataActions';
6 | import { getLanguageData } from '../../../util/languages/languages';
7 | import TextEditor from '../components/TextEditor';
8 |
9 | const mapStateToProps = (state, ownProps) => {
10 | const { uid, mostRecentProgram } = state.userData;
11 |
12 | // program data should be an object representing the most recent program
13 | // should have 2 keys, code (which is the code) and langauge (which is the language the code is written it)
14 | // add key dirty
15 | const programData = state.programs.get(mostRecentProgram, Immutable.Map()).toJS();
16 | return {
17 | ...programData,
18 | language: getLanguageData(programData.language),
19 | mostRecentProgram,
20 | theme: ownProps.theme,
21 | uid,
22 | };
23 | };
24 |
25 | const mapDispatchToProps = (dispatch, _ownProps) => ({
26 | setProgramCode: (program, code) => {
27 | dispatch(setProgramCode(program, code));
28 | },
29 | dirtyCode: (program) => {
30 | dispatch(setProgramDirty(program, true));
31 | },
32 | addProgram: (program, data) => dispatch(addProgram(program, data)),
33 | setMostRecentProgram: (value) => dispatch(setMostRecentProgram(value)),
34 | });
35 |
36 | const TextEditorContainer = connect(mapStateToProps, mapDispatchToProps)(TextEditor);
37 |
38 | export default TextEditorContainer;
39 |
--------------------------------------------------------------------------------
/src/components/common/DropdownButton.jsx:
--------------------------------------------------------------------------------
1 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2 | import { useState } from 'react';
3 | import {
4 | Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
5 | } from 'reactstrap';
6 |
7 | /** --------Props---------------
8 | * dropDownItems: array of data for each dropdown item
9 | * displayValue: string to be displayed as the placeholder for the dropdown
10 | * toggleProps: props that dictate the styling of dropDownToggle
11 | * icon: contains optional icon for editor dropDown
12 | * displayClass: contains different classes for editor and sketch dropdownbutton
13 | */
14 | /** --------Optional props--------
15 | * defaultOpen: boolean determining if the dropdown should start off open or closed
16 | */
17 |
18 | function DropdownButton(props) {
19 | const {
20 | uid,
21 | icon,
22 | DropdownItems,
23 | defaultOpen,
24 | displayValue,
25 | displayClass,
26 | toggleProps,
27 | onSelect,
28 | dirty,
29 | } = props;
30 |
31 | const dropDownParentClass = `${displayClass}-language-dropdown`;
32 | const dropDownItemClass = `${displayClass}-language-dropdown-closed-content`;
33 |
34 | const [dropdownOpen, setdropdownOpen] = useState(defaultOpen || false);
35 |
36 | const toggleHandler = () => {
37 | setdropdownOpen(!dropdownOpen);
38 | };
39 |
40 | const renderDropdownItems = () => DropdownItems.map(({ display, value, icon: itemIcon }) => (
41 | onSelect({
44 | display,
45 | value,
46 | dirty,
47 | uid,
48 | })}
49 | >
50 |
51 | {display}
52 |
53 | ));
54 |
55 | return (
56 |
57 |
toggleHandler(dropdownOpen)}>
58 | {/* eslint-disable-next-line react/jsx-props-no-spreading */}
59 |
60 |
61 |
62 | {displayValue}
63 |
64 |
65 | {renderDropdownItems()}
66 |
67 |
68 | );
69 | }
70 |
71 | export default DropdownButton;
72 |
--------------------------------------------------------------------------------
/src/components/common/Footer.tsx:
--------------------------------------------------------------------------------
1 | import TLALogo from '../../img/tla-logo.svg';
2 | import '../../styles/Footer.scss';
3 |
4 | const Footer = () => {
5 | return (
6 |
15 | );
16 | };
17 |
18 | export default Footer;
19 |
--------------------------------------------------------------------------------
/src/components/common/ImageSelector.jsx:
--------------------------------------------------------------------------------
1 | import ReactModal from 'react-modal';
2 | import { Container } from 'reactstrap';
3 | import '../../styles/ImageSelector.scss';
4 |
5 | const ImageSelector = function (props) {
6 | // Extracting all data from props
7 | const {
8 | isOpen, closeModal, maxWidth, thumbnailPreview, icons, error, children,
9 | } = props;
10 | return (
11 |
18 |
19 |
20 |
Choose a thumbnail
21 |
22 | {thumbnailPreview || null}
23 |
24 |
25 |
26 | {icons}
27 |
28 | {error ||
}
29 |
30 | {children}
31 | {' '}
32 | {/* Footer buttons as passed in as children */}
33 |
34 |
35 | );
36 | };
37 |
38 | export default ImageSelector;
39 |
--------------------------------------------------------------------------------
/src/components/common/LoadingPage.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { RingLoader } from 'react-spinners';
3 | import '../../styles/Loading.scss';
4 | import { GH_REPO_NAME } from '../../constants';
5 |
6 | interface LoadingProps {
7 | showHelpText: boolean;
8 | }
9 |
10 | const Loading = function ({ showHelpText = false } : LoadingProps) {
11 | const [hasHelpText, setHasHelpText] = useState(showHelpText);
12 |
13 | const timer = setTimeout(
14 | () => {
15 | setHasHelpText(true);
16 | },
17 | 2000,
18 | );
19 |
20 | useEffect(() => () => {
21 | clearTimeout(timer);
22 | }, []);
23 |
24 | return (
25 |
26 |
Loading
27 |
28 | { hasHelpText && (
29 |
30 | Looks like loading is taking a bit long! If it takes too long, submit an issue on
31 | {' '}
32 |
33 |
34 | Github
35 |
36 | .
37 |
38 | )}
39 |
40 | );
41 | };
42 |
43 | export default Loading;
44 |
--------------------------------------------------------------------------------
/src/components/common/OpenPanelButton.tsx:
--------------------------------------------------------------------------------
1 | import { faBars } from '@fortawesome/free-solid-svg-icons';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 |
4 | /** -------Props--------
5 | * panelOpen: boolean, is profile panel visible
6 | * togglePanel: function to call when you want the Profile Panel to disappear/reappear
7 | */
8 |
9 | interface OpenPanelButtonProps {
10 | panelOpen: boolean;
11 | togglePanel: () => void;
12 | }
13 |
14 | const OpenPanelButton = function ({ panelOpen, togglePanel }: OpenPanelButtonProps) {
15 | // if the left panel is closed, show nothing
16 | // otherwise show hamburger icon
17 | if (panelOpen) {
18 | return ;
19 | }
20 | return (
21 |
24 | );
25 | };
26 |
27 | export default OpenPanelButton;
28 |
--------------------------------------------------------------------------------
/src/components/common/SketchBox.tsx:
--------------------------------------------------------------------------------
1 | import '../../styles/SketchBox.scss';
2 | import { faDownload, faEdit, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 |
5 | import { Link } from 'react-router-dom';
6 | import { Row, Col } from 'reactstrap';
7 |
8 | interface SketchBoxProps {
9 | img: string;
10 | icon: any;
11 | name: string;
12 | deleteFunc: () => void;
13 | downloadFunc: () => void;
14 | editFunc: () => void;
15 | redirFunc: () => void;
16 | pathname: string;
17 | }
18 |
19 | function SketchBox({
20 | img, icon, name, deleteFunc, downloadFunc, editFunc, redirFunc, pathname,
21 | }: SketchBoxProps) {
22 | const buttonData = [
23 | {
24 | func: editFunc,
25 | icon: faEdit,
26 | },
27 | {
28 | func: downloadFunc,
29 | icon: faDownload,
30 | },
31 | {
32 | func: deleteFunc,
33 | icon: faTrashAlt,
34 | },
35 | ];
36 |
37 | return (
38 |
39 |
44 |

49 |
50 | {name}
51 |
52 |
53 |
54 |
55 |
56 | {buttonData
57 | .map((button) => (
58 |
59 |
60 |
61 | ))
62 | // put a thin divider between each button
63 | .flatMap((e, i) => [e, ])
64 | .slice(0, -1)}
65 |
66 |
67 | );
68 | }
69 |
70 | export default SketchBox;
71 |
--------------------------------------------------------------------------------
/src/components/common/Switch.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import '../../styles/Switch.scss';
3 |
4 | /**
5 | * Generic switch component. Props are as follows:
6 | * @param {boolean} [on] - whether the switch is set to the "on" position or not; optional prop, defaults to false
7 | * @param {function} onToggle - function to be called when switch is toggled. Has a single parameter
8 | * (bool) that holds whether the switch is in the "on" position (true) or not (false)
9 | * @param {JSX} onImg (optional): (JSX) element to be displayed on switch body when set to "on"
10 | * @param {JSX} offImg (optional): (JSX) element to be displayed on switch body when set to "off"
11 | */
12 | interface SwitchProps {
13 | on: boolean;
14 | onToggle: (on:boolean) => void;
15 | onImg?: JSX.Element;
16 | offImg?: JSX.Element;
17 |
18 | }
19 | const Switch = function (props: SwitchProps) {
20 | const [on, setOn] = useState(!props.on ? false : props.on);
21 |
22 | useEffect(() => {
23 | if (props.on !== on) {
24 | setOn(props.on);
25 | }
26 | });
27 |
28 | const onSwitchChange = () => {
29 | props.onToggle(!on);
30 | setOn(!on);
31 | };
32 |
33 | const switchedClass = on ? ' switch-on' : '';
34 |
35 | return (
36 |
43 | );
44 | };
45 |
46 | export default Switch;
47 |
--------------------------------------------------------------------------------
/src/components/common/ViewportAwareButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button, ButtonProps } from 'reactstrap';
3 |
4 | /** -------Props--------
5 | * isSmall: dictates display of text field
6 | * icon: desired icon (i.e. )
7 | * text: the text label for the button
8 | */
9 | /** --------Optional props--------
10 | * any props you would apply to a reactstrap Button.
11 | */
12 |
13 | interface ViewportAwareButtonProps extends ButtonProps {
14 | icon: React.JSX.Element,
15 | text: string,
16 | isSmall: boolean,
17 | };
18 |
19 | const ViewportAwareButton = function (props : ViewportAwareButtonProps) {
20 | // extract icon and text from props.
21 | const {
22 | icon, text, isSmall, ...remainder
23 | } = props;
24 |
25 | // render icon always, text only if not small.
26 | return (
27 |
36 | );
37 | };
38 |
39 | export default ViewportAwareButton;
40 |
--------------------------------------------------------------------------------
/src/components/common/containers/OpenPanelButtonContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { togglePanel } from '../../../actions/uiActions';
3 | import OpenPanelButton from '../OpenPanelButton';
4 |
5 | const mapStateToProps = (state) => ({
6 | panelOpen: state.ui.panelOpen,
7 | });
8 |
9 | const mapDispatchToProps = (dispatch) => ({
10 | togglePanel: () => dispatch(togglePanel()),
11 | });
12 |
13 | const OpenPanelButtonContainer = connect(mapStateToProps, mapDispatchToProps)(OpenPanelButton);
14 |
15 | export default OpenPanelButtonContainer;
16 |
--------------------------------------------------------------------------------
/src/components/common/containers/ProfilePanelContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { togglePanel } from '../../../actions/uiActions';
3 | import { setDisplayName, setPhotoName } from '../../../actions/userDataActions';
4 | import { DEFAULT_PHOTO_NAME, CLOSED_PANEL_LEFT, OPEN_PANEL_LEFT } from '../../../constants';
5 | import * as fetch from '../../../lib/fetch';
6 | import ProfilePanel from '../ProfilePanel';
7 |
8 | const mapStateToProps = (state, ownProps) => ({
9 | displayName: state.userData.displayName,
10 | uid: state.userData.uid,
11 | photoName: state.userData.photoName || DEFAULT_PHOTO_NAME,
12 | screenHeight: state.ui.screenHeight,
13 | left: state.ui.panelOpen ? OPEN_PANEL_LEFT : CLOSED_PANEL_LEFT,
14 | theme: ownProps.theme,
15 | developerAcc: state.userData.developerAcc,
16 | });
17 |
18 | const mapDispatchToProps = (dispatch, ownProps) => ({
19 | onThemeChange: ownProps.onThemeChange,
20 | collectUserPhoto: () => {},
21 | setDisplayName: (name, uid) => {
22 | try {
23 | fetch.updateUserData(uid, { displayName: name }).catch((err) => {
24 | console.error(err);
25 | });
26 | } catch (err) {
27 | console.error(err);
28 | }
29 | dispatch(setDisplayName(name));
30 | },
31 | setPhotoName: (name, uid) => {
32 | try {
33 | fetch
34 | .updateUserData(uid, { photoName: name })
35 | .then(() => {
36 | // TODO: if nothing went bad, keep the display name,
37 | // otherwise, change it back (or dont, depends how we wanna do it)
38 | })
39 | .catch((err) => {
40 | console.error(err);
41 | });
42 | } catch (err) {
43 | console.error(err);
44 | }
45 | dispatch(setPhotoName(name));
46 | },
47 | togglePanel: () => {
48 | dispatch(togglePanel());
49 | },
50 | });
51 |
52 | const ProfilePanelContainer = connect(mapStateToProps, mapDispatchToProps)(ProfilePanel);
53 |
54 | export default ProfilePanelContainer;
55 |
--------------------------------------------------------------------------------
/src/components/containers/AppContainer.js:
--------------------------------------------------------------------------------
1 | /*
2 | used to link state to App which contains the router, could link functions,
3 | but rather do that in each individual container
4 | */
5 | import { connect } from 'react-redux';
6 | import { clearClasses } from '../../actions/classesActions';
7 | import { loadPrograms, clearPrograms } from '../../actions/programsActions';
8 | import { screenResize } from '../../actions/uiActions';
9 | import { loadUserData, clearUserData, loadFailure } from '../../actions/userDataActions';
10 | import * as fetch from '../../lib/fetch';
11 | import App from '../app';
12 |
13 | const mapStateToProps = (state) => ({
14 | uid: state.userData.uid,
15 | developerAcc: state.userData.developerAcc,
16 | errorMsg: state.userData.error,
17 | screenWidth: state.ui.screenWidth,
18 | screenHeight: state.ui.screenHeight,
19 | });
20 |
21 | const mapDispatchToProps = (dispatch) => ({
22 | loadUserData: async (uid, onFailure) => {
23 | const { ok, data, error } = await fetch.getUserData(uid, true);
24 | // if the request went fine, and there's a non empty userData
25 | if (ok && data && data.userData && Object.keys(data.userData).length) {
26 | if (data.programs) {
27 | dispatch(loadPrograms(data.programs));
28 | }
29 | dispatch(loadUserData(uid, data.userData));
30 | } else {
31 | console.error('Error getting user data:', error);
32 | onFailure('SERVER ERROR: Unable to get user data from server');
33 | }
34 | },
35 | clearUserData: () => {
36 | dispatch(clearUserData());
37 | dispatch(clearPrograms());
38 | dispatch(clearClasses());
39 | },
40 | loadFailure: (err) => {
41 | dispatch(loadFailure(err));
42 | },
43 | screenResize: (width, height) => dispatch(screenResize(width, height)),
44 | });
45 |
46 | const Root = connect(mapStateToProps, mapDispatchToProps)(App);
47 |
48 | export default Root;
49 |
--------------------------------------------------------------------------------
/src/components/containers/LoginContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { loadUserData, clearUserData, loadFailure } from '../../actions/userDataActions';
3 | import Login from '../Login';
4 |
5 | const mapStateToProps = (state) => ({
6 | loggedIn: state.userData,
7 | });
8 |
9 | const mapDispatchToProps = (dispatch) => ({
10 | loadUserData: (user) => {
11 | dispatch(loadUserData(user));
12 | },
13 | clearUserData: () => {
14 | dispatch(clearUserData());
15 | },
16 | loadFailure: (err) => {
17 | dispatch(loadFailure(err));
18 | },
19 | });
20 |
21 | const LoginPage = connect(
22 | mapStateToProps,
23 | mapDispatchToProps,
24 | )(Login);
25 |
26 | export default LoginPage;
27 |
--------------------------------------------------------------------------------
/src/components/containers/MainContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { setOutput } from '../../actions/outputActions';
3 | import { setProgramDirty } from '../../actions/programsActions';
4 | import { togglePanel, setTheme } from '../../actions/uiActions';
5 |
6 | import { setMostRecentProgram } from '../../actions/userDataActions';
7 | import { CLOSED_PANEL_LEFT, OPEN_PANEL_LEFT, PANEL_SIZE } from '../../constants';
8 | import { getLanguageData } from '../../util/languages/languages';
9 | import Main from '../Main';
10 |
11 | const mapStateToProps = (state) => {
12 | const { mostRecentProgram } = state.userData;
13 |
14 | // program data should be an object representing the most recent program
15 | // should have 2 keys, code (which is the code) and langauge (which is the language the code is written it)
16 | const code = state.programs.getIn([mostRecentProgram, 'code'], undefined);
17 | const dirty = state.programs.getIn([mostRecentProgram, 'dirty'], false);
18 | const name = state.programs.getIn([mostRecentProgram, 'name'], 'untitled');
19 | const language = state.programs.getIn([mostRecentProgram, 'language'], 'txt');
20 |
21 | const listOfPrograms = [];
22 |
23 | state.programs.keySeq().forEach((key) => listOfPrograms.push(key));
24 |
25 | return {
26 | uid: state.userData.uid,
27 | mostRecentProgram,
28 | code,
29 | listOfPrograms,
30 | screenWidth: state.ui.screenWidth,
31 | screenHeight: state.ui.screenHeight,
32 | dirty,
33 | panelOpen: state.ui.panelOpen,
34 | left: (state.ui.panelOpen ? OPEN_PANEL_LEFT : CLOSED_PANEL_LEFT) + PANEL_SIZE,
35 | sketchName: name,
36 | language: getLanguageData(language),
37 | theme: state.ui.theme,
38 | };
39 | };
40 |
41 | const mapDispatchToProps = (dispatch) => ({
42 | setMostRecentProgram: (value) => dispatch(setMostRecentProgram(value)),
43 | runCode: (code, language) => dispatch(setOutput(code, language)),
44 | cleanCode: (program) => dispatch(setProgramDirty(program, false)),
45 | togglePanel: () => dispatch(togglePanel()),
46 | setTheme: (theme) => dispatch(setTheme(theme)),
47 | });
48 |
49 | const MainContainer = connect(mapStateToProps, mapDispatchToProps)(Main);
50 |
51 | export default MainContainer;
52 |
--------------------------------------------------------------------------------
/src/components/containers/ViewOnlyContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { setOutput } from '../../actions/outputActions';
3 | import { setProgramCode, setProgramLanguage } from '../../actions/programsActions';
4 | import { togglePanel, setTheme } from '../../actions/uiActions';
5 |
6 | import { CLOSED_PANEL_LEFT, OPEN_PANEL_LEFT, PANEL_SIZE } from '../../constants';
7 | import ViewOnly from '../ViewOnly';
8 |
9 | const mapStateToProps = (state) => {
10 | const { mostRecentProgram } = state.userData;
11 | return {
12 | uid: state.userData.uid,
13 | screenWidth: state.ui.screenWidth,
14 | screenHeight: state.ui.screenHeight,
15 | panelOpen: state.ui.panelOpen,
16 | left: (state.ui.panelOpen ? OPEN_PANEL_LEFT : CLOSED_PANEL_LEFT) + PANEL_SIZE,
17 | theme: state.ui.theme,
18 | mostRecentProgram,
19 | };
20 | };
21 |
22 | const mapDispatchToProps = (dispatch) => ({
23 | runCode: (code, language) => dispatch(setOutput(code, language)),
24 | togglePanel: () => dispatch(togglePanel()),
25 | setProgramCode: (program, code) => {
26 | dispatch(setProgramCode(program, code));
27 | },
28 | setProgramLanguage: (program, lang) => {
29 | dispatch(setProgramLanguage(program, lang));
30 | },
31 | setTheme: (theme) => dispatch(setTheme(theme)),
32 | });
33 |
34 | const ViewOnlyContainer = connect(mapStateToProps, mapDispatchToProps)(ViewOnly);
35 |
36 | export default ViewOnlyContainer;
37 |
--------------------------------------------------------------------------------
/src/history.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory } from 'history';
2 |
3 | export default createBrowserHistory();
4 |
--------------------------------------------------------------------------------
/src/img/background1.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/img/blueguy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/src/img/blueguy.png
--------------------------------------------------------------------------------
/src/img/tla-logo-green.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/img/tla-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-underscore-dangle */
2 | import ReactDOM from 'react-dom/client';
3 | import 'bootstrap/dist/css/bootstrap.css';
4 | import './styles/global.scss';
5 | import { Provider } from 'react-redux';
6 | import Root from './components/containers/AppContainer';
7 | import registerServiceWorker from './registerServiceWorker';
8 | import store from './store';
9 |
10 | const root = ReactDOM.createRoot(document.getElementById('root')!);
11 |
12 | root.render(
13 |
14 |
15 | ,
16 | );
17 |
18 | registerServiceWorker();
19 |
--------------------------------------------------------------------------------
/src/lib/cookies.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * an internal helper function to get a cookie from the document.
3 | * almost word-for-word copied from https://www.w3schools.com/js/js_cookies.asp
4 | * @param {string} cname - the key value for the requested cookie
5 | * @return {string} the value of the requested key, "" for none set
6 | */
7 |
8 | function getCookie(cname: string): string {
9 | const name = `${cname}=`;
10 | const decodedCookie = decodeURIComponent(document.cookie);
11 | const ca = decodedCookie.split(';');
12 | for (let i = 0; i < ca.length; i++) {
13 | let c = ca[i];
14 | while (c.charAt(0) === ' ') {
15 | c = c.substring(1);
16 | }
17 | if (c.indexOf(name) === 0) {
18 | return c.substring(name.length, c.length);
19 | }
20 | }
21 | return '';
22 | }
23 |
24 | /**
25 | * an internal helpder function to set a cookie in the document
26 | * almost word-for-word copied from https://www.w3schools.com/js/js_cookies.asp
27 | * @param {string} cname - the key value for the requested cookie
28 | * @param {string} cvalue - the value to set the cookie to
29 | * @param {number} exdays - the number of days until the cookie expires
30 | */
31 |
32 | function setCookie(cname: string, cvalue: string, exdays: number) {
33 | const d = new Date();
34 | d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
35 | const expires = `expires=${d.toUTCString()}`;
36 | document.cookie = `${cname}=${cvalue};${expires};path=/`;
37 | }
38 |
39 | /**
40 | * exported function that gets the current app theme from the theme cookie;
41 | * if none is set, returns dark and sets the cookie to dark
42 | * @returns {string} the current theme, or dark if the cookie doesn't exist
43 | */
44 |
45 | export const getThemeFromCookie = (): string => {
46 | const theme = getCookie('theme');
47 | if (theme !== '') {
48 | return theme;
49 | }
50 | setCookie('theme', 'dark', 365);
51 | return 'dark';
52 | };
53 |
54 | /**
55 | * exported function that sets the theme cookie to the input, with a one-year expiry date
56 | * @param {string} theme theme string to store in the cookie
57 | */
58 |
59 | export const setThemeCookie = (theme: string) => {
60 | setCookie('theme', theme, 365);
61 | };
62 |
63 | /**
64 | * exported function that toggles the theme cookie to flip-flop its value from light to dark
65 | * if none is set, assumes that the user is currently in dark and switches to light
66 | * @returns {string} returns the current value of the theme cookie
67 | */
68 |
69 | export const toggleCookie = (): string => {
70 | if (getThemeFromCookie() === 'light') {
71 | setCookie('theme', 'dark', 365);
72 | return 'dark';
73 | }
74 | setCookie('theme', 'light', 365);
75 | return 'light';
76 | };
77 |
--------------------------------------------------------------------------------
/src/lib/sketch.ts:
--------------------------------------------------------------------------------
1 | import constants from '../constants';
2 |
3 | /**
4 | * constructShareableSketchURL: given a program ID, generate
5 | * a shareable link to the view-only version of that program.
6 | * checks for the current hostname using window.location.hostname,
7 | * @param {String} programId the id of the program
8 | * @param {String} [domainRoot=window.location.hostname] domainRoot (e.g. editor.uclaacm.com).
9 | * defaults localhost to 8080
10 | */
11 |
12 | export const constructShareableSketchURL = (programId: string, domainRoot: string = window.location.hostname) => {
13 | let root;
14 | if (domainRoot.includes('localhost')) {
15 | root = 'http://localhost:8080';
16 | } else {
17 | root = `https://${domainRoot}`;
18 | }
19 |
20 | return `${root}${constants.ROUTER_BASE_NAME}p/${programId}`;
21 | };
22 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/reducers/classesReducer.js:
--------------------------------------------------------------------------------
1 | import Immutable, { Map } from 'immutable';
2 |
3 | import {
4 | ADD_STUDENT_CLASS,
5 | REMOVE_STUDENT_CLASS,
6 | LOAD_STUDENT_CLASSES,
7 | CLEAR_STUDENT_CLASSES,
8 | ADD_INSTR_CLASS,
9 | REMOVE_INSTR_CLASS,
10 | LOAD_INSTR_CLASSES,
11 | CLEAR_INSTR_CLASSES,
12 | CLEAR_CLASSES,
13 | } from '../actions/classesActions';
14 |
15 | const initialState = Map({ studClasses: Map(), instrClasses: Map() });
16 |
17 | function classesReducer(state = initialState, action) {
18 | switch (action.type) {
19 | case ADD_STUDENT_CLASS:
20 | return state.setIn(['studClasses', action.cid], Immutable.fromJS(action.data));
21 | case REMOVE_STUDENT_CLASS:
22 | return state.deleteIn(['studClasses', action.cid]);
23 | case LOAD_STUDENT_CLASSES: {
24 | // TODO: update this to make sure classes are indexed by class ID
25 | // Want to extract CIDs from action.classes and use them as the keys.
26 | const studClassesKeyed = {};
27 | action.classes.forEach((classObj) => {
28 | studClassesKeyed[classObj.cid] = classObj;
29 | });
30 | return state.set('studClasses', Immutable.fromJS(studClassesKeyed));
31 | }
32 | case CLEAR_STUDENT_CLASSES:
33 | return state.set('studClasses', Map());
34 | case ADD_INSTR_CLASS:
35 | return state.setIn(['instrClasses', action.cid], Immutable.fromJS(action.data));
36 | case REMOVE_INSTR_CLASS:
37 | return state.deleteIn(['instrClasses', action.cid]);
38 | case LOAD_INSTR_CLASSES: {
39 | // TODO: update this to make sure classes are indexed by class ID
40 | const instrClassesKeyed = {};
41 | action.classes.forEach((classObj) => {
42 | instrClassesKeyed[classObj.cid] = classObj;
43 | });
44 | return state.set('instrClasses', Immutable.fromJS(instrClassesKeyed));
45 | }
46 | case CLEAR_INSTR_CLASSES:
47 | return state.set('instrClasses', Map());
48 | case CLEAR_CLASSES:
49 | return Map({ studClasses: Map(), instrClasses: Map() });
50 | default:
51 | return state;
52 | }
53 | }
54 |
55 | export default classesReducer;
56 |
--------------------------------------------------------------------------------
/src/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import classesReducer from './classesReducer';
3 | import outputReducer from './outputReducer';
4 | import programsReducer from './programsReducer';
5 | import uiReducer from './uiReducer';
6 | import userDataReducer from './userDataReducer';
7 |
8 | const appReducers = combineReducers({
9 | userData: userDataReducer,
10 | output: outputReducer,
11 | programs: programsReducer,
12 | classes: classesReducer,
13 | ui: uiReducer,
14 | });
15 |
16 | export default appReducers;
17 |
--------------------------------------------------------------------------------
/src/reducers/outputReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | CLEAR_OUTPUT,
3 | SET_RUN_RESULT,
4 | SET_OUTPUT_LANGUAGE,
5 | SET_OUTPUT,
6 | } from '../actions/outputActions';
7 | import { getLanguageData } from '../util/languages/languages';
8 |
9 | const initialState = {
10 | runResult: '',
11 | language: getLanguageData('python'),
12 | };
13 |
14 | function outputReducer(state = initialState, action) {
15 | switch (action.type) {
16 | case CLEAR_OUTPUT:
17 | return initialState;
18 | case SET_RUN_RESULT:
19 | return { ...state, runResult: action.value };
20 | case SET_OUTPUT_LANGUAGE:
21 | return { ...state, language: action.value };
22 | case SET_OUTPUT:
23 | return { ...state, runResult: action.runResult, language: action.language };
24 | default:
25 | return state;
26 | }
27 | }
28 |
29 | export default outputReducer;
30 |
--------------------------------------------------------------------------------
/src/reducers/programsReducer.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'immutable';
2 | import {
3 | SET_PROGRAM_CODE,
4 | SET_PROGRAM_LANGUAGE,
5 | SET_PROGRAM_NAME,
6 | SET_PROGRAM_THUMBNAIL,
7 | DELETE_PROGRAM,
8 | LOAD_PROGRAMS,
9 | CLEAR_PROGRAMS,
10 | SET_PROGRAM_DIRTY,
11 | ADD_PROGRAM,
12 | } from '../actions/programsActions';
13 |
14 | const initialState = Immutable.Map();
15 |
16 | function programsReducer(state = initialState, action) {
17 | switch (action.type) {
18 | case LOAD_PROGRAMS:
19 | return Immutable.fromJS(action.programs);
20 | case SET_PROGRAM_CODE:
21 | return state.setIn([action.program, 'code'], action.value);
22 | case SET_PROGRAM_LANGUAGE:
23 | return state.setIn([action.program, 'language'], action.value);
24 | case SET_PROGRAM_DIRTY:
25 | return state.setIn([action.program, 'dirty'], action.value);
26 | case SET_PROGRAM_NAME:
27 | return state.setIn([action.program, 'name'], action.value);
28 | case SET_PROGRAM_THUMBNAIL:
29 | return state.setIn([action.program, 'thumbnail'], action.value);
30 | case ADD_PROGRAM:
31 | return state.set(action.program, Immutable.fromJS(action.data));
32 | case DELETE_PROGRAM:
33 | return state.delete(action.program);
34 | case CLEAR_PROGRAMS:
35 | return Immutable.Map();
36 | default:
37 | return state;
38 | }
39 | }
40 |
41 | export default programsReducer;
42 |
--------------------------------------------------------------------------------
/src/reducers/uiReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | SCREEN_RESIZE,
3 | TOGGLE_PANEL,
4 | SET_PANEL,
5 | SET_THEME,
6 | SET_CLASSES_LOADED,
7 | SET_ON_INSTR_VIEW,
8 | } from '../actions/uiActions';
9 |
10 | const initialState = {
11 | screenWidth: typeof window === 'object' ? window.innerWidth : null,
12 | screenHeight: typeof window === 'object' ? window.innerHeight : null,
13 | panelOpen: false,
14 | theme: 'dark',
15 | classesLoaded: false,
16 | onInstrView: false,
17 | };
18 |
19 | function uiReducer(state = initialState, action) {
20 | switch (action.type) {
21 | case SCREEN_RESIZE:
22 | return { ...state, screenWidth: action.width, screenHeight: action.height };
23 | case TOGGLE_PANEL:
24 | return { ...state, panelOpen: !state.panelOpen };
25 | case SET_PANEL:
26 | return { ...state, panelOpen: action.value };
27 | case SET_THEME:
28 | return { ...state, theme: action.theme };
29 | case SET_CLASSES_LOADED:
30 | return { ...state, classesLoaded: action.value };
31 | case SET_ON_INSTR_VIEW:
32 | return { ...state, onInstrView: action.value };
33 | default:
34 | return state;
35 | }
36 | }
37 |
38 | export default uiReducer;
39 |
--------------------------------------------------------------------------------
/src/reducers/userDataReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | LOAD_USER_DATA,
3 | CLEAR_USER_DATA,
4 | LOAD_FAILURE,
5 | SET_DISPLAY_NAME,
6 | SET_MOST_RECENT_PROGRAM,
7 | SET_PHOTO_NAME,
8 | SET_CURRENT_CLASS,
9 | } from '../actions/userDataActions';
10 |
11 | const initialState = {
12 | error: '',
13 | displayName: '',
14 | uid: '',
15 | mostRecentProgram: '',
16 | photoName: '',
17 | currentClass: '',
18 | };
19 |
20 | // eslint-disable-next-line default-param-last
21 | function userDataReducer(state = initialState, action) {
22 | switch (action.type) {
23 | case LOAD_USER_DATA:
24 | // pull all values we want to pay attention to out of the object
25 | return { ...state, ...action.userData };
26 | case CLEAR_USER_DATA:
27 | return initialState;
28 | case LOAD_FAILURE:
29 | return { ...state, error: action.message };
30 | case SET_DISPLAY_NAME:
31 | return { ...state, displayName: action.value };
32 | case SET_PHOTO_NAME:
33 | return { ...state, photoName: action.photoName };
34 | case SET_MOST_RECENT_PROGRAM:
35 | return { ...state, mostRecentProgram: action.value };
36 | case SET_CURRENT_CLASS:
37 | return { ...state, currentClass: action.value };
38 | default:
39 | return state;
40 | }
41 | }
42 | export default userDataReducer;
43 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // test-setup.js
2 | import * as enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 |
5 | enzyme.configure({ adapter: new Adapter() });
6 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import { thunk } from 'redux-thunk';
3 | import appReducers from './reducers';
4 |
5 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
6 | const store = createStore(appReducers, composeEnhancers(applyMiddleware(thunk)));
7 |
8 | export default store;
9 |
--------------------------------------------------------------------------------
/src/styles/ClassBox.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 |
4 | .class-thumbnail {
5 | display: block;
6 | width: 80px;
7 | height: 80px;
8 | margin-left: 0px;
9 | }
10 |
11 | .class-box {
12 | display: flex;
13 | flex-direction: row;
14 | justify-content: flex-start;
15 | align-items: center;
16 | height: 9em;
17 | width: 95%;
18 | border-radius: 5px;
19 | margin: 10px;
20 | color: $theme-dark;
21 | cursor: pointer;
22 | flex: 0 0 auto;
23 | @include themify($themes) {
24 | color: themed("cardColor");
25 | background-color: themed("cardBackground");
26 | border: themed("cardBorder")
27 | }
28 | }
29 |
30 | .class-box.join-class {
31 | height: 5em;
32 | padding-left: 20px;
33 | padding-right: 20px;
34 | }
35 |
36 | .class-box-body {
37 | color: inherit;
38 | display: inherit;
39 | width: 90%;
40 | margin-left: 20px;
41 | margin-right: 20px;
42 | }
43 |
44 | .class-box-body:hover {
45 | color: inherit;
46 | text-decoration: none;
47 | }
48 |
49 | .class-info {
50 | margin-left: 20px;
51 | padding-top: 10px;
52 | }
53 |
54 | .instructor-name {
55 | margin-top: 15px;
56 | }
57 |
58 | .sketch-divider {
59 | width: 100%;
60 | margin: 0;
61 | padding: 0;
62 | @include themify($themes){
63 | border-top: 1px solid themed("cardDivider");
64 | }
65 | }
66 |
67 | .sketch-button-divider {
68 | width: 1px;
69 | @include themify($themes){
70 | border: 0.5px solid themed("cardDivider");
71 | }
72 | }
73 |
74 | .sketch-name {
75 | width: 85%;
76 | text-overflow: ellipsis;
77 | white-space: nowrap;
78 | overflow: hidden;
79 | }
80 |
81 | .sketch-icon {
82 | width: 85%;
83 | text-overflow: ellipsis;
84 | white-space: nowrap;
85 | overflow: hidden;
86 | }
87 |
88 | .sketch-metadata{
89 | display: flex;
90 | width: 100%;
91 | flex-direction: row;
92 | justify-content: space-between;
93 | align-items: center;
94 | }
95 |
--------------------------------------------------------------------------------
/src/styles/ClassPage.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .add-sketch-box {
4 | height: 153px;
5 | }
6 |
7 | .add-sketch-box-body {
8 | align-items: center;
9 | display: flex;
10 | flex-direction: column;
11 | }
12 |
13 | .add-sketch-plus {
14 | margin: 40px 0 19px;
15 | }
16 |
17 | .back-btn {
18 | margin-left: 10px;
19 | }
20 |
21 | .class-content-container {
22 | display: flex;
23 | flex-direction: column;
24 | justify-content: flex-start;
25 | align-items: center;
26 | margin: 20px 50px;
27 | overflow: auto;
28 | }
29 |
30 | .section-header {
31 | font-size: 1.5rem;
32 | margin-bottom: 35px;
33 | }
34 |
35 | .section-content {
36 | font-size: 1.25rem;
37 | }
38 |
39 | .class-info-box {
40 | width: 100%;
41 | margin: 5px 10px 25px;
42 | }
43 |
44 | .class-sketches-grid {
45 | display: flex;
46 | flex-direction: column;
47 | justify-content: flex-start;
48 | align-items: flex-start;
49 | width: 100%;
50 | overflow-y: auto;
51 | overflow-x: hidden;
52 | }
53 |
54 | .class-sketches-grid-row {
55 | display: flex;
56 | flex-direction: row;
57 | justify-content: flex-start;
58 | align-items: center;
59 | width: 100%;
60 | padding: 20px 0;
61 | }
62 |
--------------------------------------------------------------------------------
/src/styles/Classes.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .classes-container {
4 | flex-direction: column;
5 | align-items: flex-start;
6 | @include themify($themes) {
7 | background-color: themed("backgroundColor");
8 | color: themed("color");
9 | }
10 | justify-content: flex-start;
11 | position: absolute;
12 | transition: all 0.5s ease;
13 | overflow: hidden;
14 | }
15 |
16 | .classes-content-container {
17 | display: flex;
18 | flex-direction: column;
19 | justify-content: flex-start;
20 | align-items: center;
21 | height: 90%;
22 | width: 100%;
23 | overflow: auto;
24 | }
25 |
26 | .classes-grid {
27 | display: flex;
28 | flex-direction: column;
29 | justify-content: flex-start;
30 | align-items: center;
31 | height: 90%;
32 | width: 100%;
33 | overflow: auto;
34 | }
35 |
36 | // a copy of .sketches-header
37 | .classes-header {
38 | height: 67px;
39 | width: 100%;
40 | display: flex;
41 | flex-direction: row;
42 | justify-content: flex-start;
43 | align-items: center;
44 | @include themify($themes) {
45 | border-bottom: 1px solid themed("color");
46 | }
47 | }
48 |
49 | .classes-header-text {
50 | font-weight: bold;
51 | padding: 1rem;
52 | cursor: pointer;
53 | flex: 0 1 auto;
54 | font-size: 45px;
55 | }
56 |
57 | .join-class-text {
58 | margin-left: 20px;
59 | }
60 |
61 | .join-class-input {
62 | font-size: 1.7em;
63 | line-height: normal;
64 | padding: 0.25rem 0.1rem 0px 0.1rem;
65 | border: 0px;
66 | @include themify($themes) {
67 | background-color: themed("backgroundColor");
68 | color: themed("color");
69 | }
70 | border-bottom: 2px solid #dddcdf;
71 | outline: 0;
72 | width: 50%;
73 | min-width: 300px;
74 | max-width: 500px;
75 | transition: all 0.5s ease;
76 | // color: #dddcdf;
77 | display: inline-block;
78 | }
79 |
80 | .join-class-button {
81 | display: inline-block;
82 | font-family: "Josefin Slab", sans-serif;
83 | // color: #dddcdf;
84 | color: white;
85 | // background-color: #857e8f;
86 | @include themify($themes) {
87 | background-color: themed("accentBackground");
88 | }
89 | cursor: pointer;
90 | overflow: visible;
91 | text-align: center;
92 | border: 0;
93 | border-radius: 3px;
94 | padding: 8px 10px 8px 10px;
95 | margin-left: 1rem;
96 | font-size: 1.7em;
97 | line-height: 1.4em;
98 | transition: all 0.5s ease;
99 | }
100 |
101 | .join-class-button:hover {
102 | /* border: 2px solid white;
103 | border-radius: 10px;
104 | /* box-shadow: inset 0 0 0 100px rgba(0, 0, 0, 0.2); */
105 | box-shadow: inset 0 0 0 1px white;
106 | }
107 |
108 | .join-class-button:disabled {
109 | opacity: 0.3;
110 | box-shadow: none;
111 | cursor: default;
112 | }
113 |
114 | .join-class-plus {
115 | display: block;
116 | width: 80px;
117 | margin-left: 0px;
118 | }
119 |
120 | .no-classes-container {
121 | margin-top: 3em;
122 | text-align: center;
123 | width: 100%;
124 | height: 100%;
125 | padding: 1em;
126 | @include themify($themes) {
127 | color: themed("color");
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/styles/Footer.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .footer {
4 | align-items: center;
5 | bottom: 0;
6 | height: 8vh; /* calc used to do arithmetic */
7 | position: absolute;
8 | text-align: center;
9 | width: 100%;
10 |
11 | @include themify($themes) {
12 | background-color: themed('accentBackground');
13 | }
14 | }
15 |
16 | .footer-image {
17 | height: 100%;
18 | }
19 |
--------------------------------------------------------------------------------
/src/styles/ImageSelector.scss:
--------------------------------------------------------------------------------
1 | .image-selector-modal {
2 | background-color: rgb(255, 255, 255);
3 | border-radius: 20px;
4 | left: 50%;
5 | min-height: 470px;
6 | padding: 40px;
7 | position: fixed;
8 | top: 50%;
9 | transform: translate(-50%, -50%);
10 | }
11 |
12 | .image-selector-modal-header {
13 | align-items: flex-start;
14 | display: flex;
15 | flex-direction: row;
16 | justify-content: space-between;
17 | text-align: left;
18 | }
19 |
20 | .image-selector-modal-header-thumbnail-container {
21 | flex: 0 0;
22 | height: 100px;
23 | width: 100px;
24 | }
25 |
26 | .image-selector-gallery {
27 | display: flex;
28 | flex-wrap: wrap;
29 | height: 250px;
30 | justify-content: space-evenly;
31 | margin-bottom: 10px;
32 | margin-left: 10px;
33 | overflow-y: auto;
34 | width: 100%;
35 | }
36 |
--------------------------------------------------------------------------------
/src/styles/Loading.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .Loading {
4 | align-items: center;
5 | background-color: $theme-light;
6 | display: flex;
7 | flex-direction: column;
8 | height: 100%;
9 | justify-content: center;
10 | width: 100%;
11 | }
12 |
13 | .Loading-title {
14 | color: $off-white;
15 | font-family: $font-family-heading;
16 | font-size: 5rem;
17 | margin: 0 0 2rem;
18 | }
19 |
20 | .Loading-page-text {
21 | color: $off-white;
22 | margin-left: 2rem;
23 | margin-right: 2rem;
24 | margin-top: 3rem;
25 | }
26 |
27 | .Loading-link-text {
28 | color: $off-white;
29 | text-decoration: underline;
30 | }
31 |
32 | .Loading-link-text:hover {
33 | color: $teachla-primary;
34 | }
35 |
--------------------------------------------------------------------------------
/src/styles/Main.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .main {
4 | align-items: flex-start;
5 | background-color: $theme-dark;
6 | display: flex;
7 | flex-direction: row;
8 | justify-content: flex-start;
9 | position: relative;
10 | }
11 |
--------------------------------------------------------------------------------
/src/styles/Modals.scss:
--------------------------------------------------------------------------------
1 | .modal-sm {
2 | background-color: rgb(255, 255, 255);
3 | border-radius: 20px;
4 | left: 50%;
5 | min-width: 400px;
6 | padding: 40px;
7 | position: fixed;
8 | top: 50%;
9 | transform: translate(-50%, -50%);
10 | width: 50%;
11 | }
12 |
13 | .modal-md {
14 | background-color: rgb(255, 255, 255);
15 | border-radius: 20px;
16 | left: 50%;
17 | min-width: 450px;
18 | padding: 40px;
19 | position: fixed;
20 | top: 50%;
21 | transform: translate(-50%, -50%);
22 | width: 50%;
23 | }
24 |
25 | .modal-overlay {
26 | background-color: rgba(59, 59, 59, 0.514);
27 | bottom: 0;
28 | left: 0;
29 | position: fixed;
30 | right: 0;
31 | top: 0;
32 | z-index: 80;
33 | }
34 |
--------------------------------------------------------------------------------
/src/styles/Output.scss:
--------------------------------------------------------------------------------
1 | .editor-output-iframe div {
2 | box-sizing: border-box;
3 | }
4 |
5 | .editor-output-iframe {
6 | display: flex;
7 | flex-flow: column;
8 | padding: 10px;
9 | }
10 |
11 | #my-canvas {
12 | background-color: #c5c5c5;
13 | border: 1px solid black;
14 | height: 40%;
15 | margin: 10px 0;
16 | }
17 |
18 | #inner {
19 | background-color: #222;
20 | border: 0;
21 | color: #ddd;
22 | font-family: monospace;
23 | height: 40%;
24 | margin: 0 auto;
25 | overflow: auto;
26 | padding: 10px 35px 10px 10px;
27 | position: relative;
28 | resize: vertical;
29 | width: 100%;
30 | word-wrap: break-word;
31 | }
32 |
--------------------------------------------------------------------------------
/src/styles/Page.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .full-container {
4 | height: 100%;
5 | width: 100%;
6 | }
7 |
8 | .page-container {
9 | align-items: center;
10 | display: flex;
11 | flex-direction: column;
12 | height: 100%;
13 | justify-content: center;
14 |
15 | @include themify($themes) {
16 | background-color: themed('backgroundColor');
17 | color: themed('color');
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/styles/Radio.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .radio-selector {
4 | display: flex;
5 | font-size: 1rem;
6 | height: 60px;
7 | }
8 |
9 | .radio-option {
10 | background-color: $theme-light;
11 | color: #eee;
12 | cursor: pointer;
13 | max-width: 70px;
14 | padding: 8px;
15 | }
16 |
17 | .radio-option:hover {
18 | box-shadow: inset 0 0 0 50px rgba(0, 0, 0, 0.2);
19 | }
20 |
21 | .radio-option-selected {
22 | background-color: $radio-purple;
23 | border: none !important;
24 | color: #fff;
25 | cursor: pointer;
26 | padding: 8px;
27 | }
28 |
29 | .radio-option-selected:hover {
30 | box-shadow: inset 0 0 0 50px rgba(0, 0, 0, 0.2);
31 | }
32 |
33 | #radio-left {
34 | border-radius: 0.3rem 0 0 0.3rem;
35 | border-right: 2px solid rgba(255, 255, 255, 0.1);
36 | }
37 |
38 | #radio-right {
39 | border-left: 2px solid rgba(255, 255, 255, 0.1);
40 | border-radius: 0 0.3rem 0.3rem 0;
41 | }
42 |
--------------------------------------------------------------------------------
/src/styles/Resizer.scss:
--------------------------------------------------------------------------------
1 | // CSS from react-split-pane on GitHub: https://github.com/tomkp/react-split-pane#example-styling
2 |
3 | .Resizer {
4 | background: #000;
5 | -moz-background-clip: padding;
6 | -webkit-background-clip: padding;
7 | background-clip: padding-box;
8 | -moz-box-sizing: border-box;
9 | -webkit-box-sizing: border-box;
10 | box-sizing: border-box;
11 | opacity: 0.2;
12 | z-index: 1;
13 | }
14 |
15 | .Resizer:hover {
16 | transition: all 2s ease;
17 | }
18 |
19 | .Resizer.horizontal {
20 | border-bottom: 5px solid rgba(255, 255, 255, 0);
21 | border-top: 5px solid rgba(255, 255, 255, 0);
22 | cursor: w-resize;
23 | height: 11px;
24 | margin: -5px 0;
25 | width: 100%;
26 | }
27 |
28 | .Resizer.horizontal:hover {
29 | border-bottom: 5px solid rgba(0, 0, 0, 0.5);
30 | border-top: 5px solid rgba(0, 0, 0, 0.5);
31 | }
32 |
33 | .Resizer.vertical {
34 | border-left: 5px solid rgba(255, 255, 255, 0);
35 | border-right: 5px solid rgba(255, 255, 255, 0);
36 | cursor: w-resize;
37 | margin: 0 -5px;
38 | width: 11px;
39 | }
40 |
41 | .Resizer.vertical:hover {
42 | border-left: 5px solid rgba(0, 0, 0, 0.5);
43 | border-right: 5px solid rgba(0, 0, 0, 0.5);
44 | }
45 |
46 | .Resizer.disabled {
47 | cursor: not-allowed;
48 | }
49 |
50 | .Resizer.disabled:hover {
51 | border-color: transparent;
52 | }
53 |
--------------------------------------------------------------------------------
/src/styles/SketchBox.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .sketch-thumbnail {
4 | display: block;
5 | height: 80px;
6 | margin-left: auto;
7 | margin-right: auto;
8 | width: 80px;
9 | }
10 |
11 | .sketch-box {
12 | align-items: center;
13 | border-radius: 5px;
14 | color: $theme-dark;
15 | cursor: pointer;
16 | display: flex;
17 | flex: 0 0 auto;
18 | flex-direction: column;
19 | justify-content: flex-start;
20 | margin: 0 10px;
21 | width: 200px;
22 |
23 | @include themify($themes) {
24 | background-color: themed('cardBackground');
25 | border: themed('cardBorder');
26 | color: themed('cardColor');
27 | }
28 | }
29 |
30 | .sketch-box-body {
31 | color: inherit;
32 | margin-left: auto;
33 | margin-right: auto;
34 | width: 90%;
35 | }
36 |
37 | .sketch-box-body:hover {
38 | color: inherit;
39 | text-decoration: none;
40 | }
41 |
42 | .sketch-divider {
43 | margin: 0;
44 | padding: 0;
45 | width: 100%;
46 |
47 | @include themify($themes) {
48 | border-top: 1px solid themed('cardDivider');
49 | }
50 | }
51 |
52 | .sketch-button-divider {
53 | width: 1px;
54 | padding: 0;
55 |
56 | @include themify($themes) {
57 | border: 0.5px solid themed('cardDivider');
58 | }
59 | }
60 |
61 | .sketch-name {
62 | overflow: hidden;
63 | text-overflow: ellipsis;
64 | white-space: nowrap;
65 | width: 85%;
66 | }
67 |
68 | .sketch-icon {
69 | overflow: hidden;
70 | text-overflow: ellipsis;
71 | white-space: nowrap;
72 | width: 85%;
73 | }
74 |
75 | .sketch-metadata {
76 | align-items: center;
77 | display: flex;
78 | flex-direction: row;
79 | justify-content: space-between;
80 | width: 100%;
81 | }
82 |
--------------------------------------------------------------------------------
/src/styles/Sketches.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .sketches-container {
4 | align-items: flex-start;
5 | flex-direction: column;
6 | justify-content: flex-start;
7 | overflow: hidden;
8 | position: absolute;
9 | transition: all 0.5s ease;
10 |
11 | @include themify($themes) {
12 | background-color: themed('backgroundColor');
13 | color: themed('color');
14 | }
15 | }
16 |
17 | .sketches-header-text {
18 | cursor: pointer;
19 | flex: 0 1 auto;
20 | font-size: 45px;
21 | font-weight: bold;
22 | padding: 1rem;
23 | }
24 |
25 | .sketches-header {
26 | align-items: center;
27 | display: flex;
28 | flex-direction: row;
29 | height: 67px;
30 | justify-content: flex-start;
31 | width: 100%;
32 |
33 | @include themify($themes) {
34 | border-bottom: 1px solid themed('color');
35 | }
36 | }
37 |
38 | .sketches-grid {
39 | align-items: flex-start;
40 | display: flex;
41 | flex-direction: column;
42 | justify-content: flex-start;
43 | overflow-x: hidden;
44 | overflow-y: auto;
45 | width: 100%;
46 | }
47 |
48 | .sketches-grid-row {
49 | align-items: center;
50 | display: flex;
51 | flex-direction: row;
52 | justify-content: flex-start;
53 | padding: 20px 50px;
54 | width: 100%;
55 | }
56 |
57 | .no-sketches-container {
58 | height: 100%;
59 | margin-top: 3em;
60 | padding: 1em;
61 | text-align: center;
62 | width: 100%;
63 |
64 | @include themify($themes) {
65 | color: themed('color');
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/styles/SketchesModal.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .sketches-language-dropdown {
4 | color: #0d0d0e;
5 | flex: 0 1 auto;
6 | }
7 |
8 | .sketches-language-dropdown-closed-content {
9 | display: inline-block;
10 | }
11 |
12 | .sketches-gallery-img {
13 | border: 3px white solid;
14 | border-radius: 100%;
15 | height: 80px;
16 | object-fit: cover;
17 | padding: 2px;
18 | width: 80px;
19 | }
20 |
21 | .sketches-gallery-img-selected {
22 | border: 3px $theme-highlight solid;
23 | border-radius: 100%;
24 | height: 80px;
25 | object-fit: cover;
26 | padding: 2px;
27 | width: 80px;
28 | }
29 |
30 | .sketches-gallery-img:hover {
31 | border: 3px $theme-light solid;
32 | border-radius: 100%;
33 | height: 80px;
34 | object-fit: cover;
35 | padding: 2px;
36 | width: 80px;
37 | }
38 |
39 | .sketches-gallery::-webkit-scrollbar {
40 | width: 10px;
41 | }
42 |
43 | /* Track */
44 | .sketches-gallery::-webkit-scrollbar-track {
45 | background: #f1f1f1;
46 | }
47 |
48 | /* Handle */
49 | .sketches-gallery::-webkit-scrollbar-thumb {
50 | background: #37343d;
51 | }
52 |
53 | /* Handle on hover */
54 | .sketches-gallery::-webkit-scrollbar-thumb:hover {
55 | background: #2f2d33;
56 | }
57 |
58 | .sketches-modal-header {
59 | align-items: flex-start;
60 | display: flex;
61 | flex-direction: row;
62 | justify-content: space-between;
63 | text-align: left;
64 | }
65 |
66 | .sketches-modal-header-thumbnail-container {
67 | flex: 0 0;
68 | height: 100px;
69 | width: 100px;
70 | }
71 |
72 | .sketches-modal-header-thumbnail {
73 | flex: 0 0;
74 | height: 100px;
75 | width: 100px;
76 | }
77 |
78 | .sketches-modal {
79 | background-color: rgb(255, 255, 255);
80 | border-radius: 20px;
81 | left: 50%;
82 | min-width: 400px;
83 | padding: 40px;
84 | position: fixed;
85 | top: 50%;
86 | transform: translate(-50%, -50%);
87 | width: 50%;
88 | }
89 |
90 | .sketches-form-spinner {
91 | align-items: center;
92 | display: flex;
93 | flex-direction: row;
94 | justify-content: center;
95 | width: 59.02px;
96 | }
97 |
--------------------------------------------------------------------------------
/src/styles/Switch.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | .switch {
4 | border-radius: 6px;
5 | box-sizing: content-box;
6 | cursor: pointer;
7 | display: block;
8 | font-family: inherit;
9 | height: 30px;
10 | padding: 0.5rem 1rem;
11 | position: relative;
12 | width: 100px;
13 | }
14 |
15 | .switch-input {
16 | box-sizing: content-box;
17 | left: 0;
18 | opacity: 0;
19 | padding: 0;
20 | position: absolute;
21 | top: 0;
22 | }
23 |
24 | .switch-body {
25 | align-items: center;
26 | background: $white;
27 | border-radius: inherit;
28 | display: flex;
29 | font-size: 1rem;
30 | height: inherit;
31 | justify-content: flex-end;
32 | padding: 10px;
33 | position: relative;
34 | transition: all $switch-transition-time;
35 |
36 | &.switch-on {
37 | background: $theme-dark;
38 | justify-content: flex-start !important;
39 | }
40 | }
41 |
42 | .switch-handle {
43 | background: $theme-dark;
44 | border-radius: 6px;
45 | box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
46 | height: 30px;
47 | position: absolute;
48 | top: 8px;
49 | transition: all $switch-transition-time;
50 | width: 30px;
51 |
52 | &.switch-on {
53 | background: white;
54 | margin-left: 70px;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/styles/app.scss:
--------------------------------------------------------------------------------
1 | .app {
2 | height: 100%;
3 | width: 100%;
4 | }
5 |
--------------------------------------------------------------------------------
/src/styles/global.scss:
--------------------------------------------------------------------------------
1 | @import 'variables.scss';
2 |
3 | html,
4 | body,
5 | #root {
6 | /* without this the background color doesn't affect the whole window */
7 | margin: 0;
8 | padding: 0;
9 | }
10 |
11 | body {
12 | box-sizing: border-box;
13 | font-family: $font-family-body;
14 | overflow: hidden;
15 | }
16 |
17 | h1,
18 | h2,
19 | h3,
20 | h4,
21 | h5,
22 | h6 {
23 | font-family: $font-family-heading;
24 | }
25 |
26 | * {
27 | /* needed for the modal's button to stay within the window */
28 | box-sizing: border-box;
29 | }
30 |
31 | .force-no-wrap {
32 | white-space: nowrap;
33 | }
34 |
35 | .teachla-green {
36 | background-clip: text;
37 | //color: #92D32B;
38 | background-color: $teachla-primary;
39 | background-image: $teachla-gradient;
40 | background-size: 100%;
41 | -webkit-text-fill-color: transparent;
42 | -moz-text-fill-color: transparent;
43 | }
44 |
45 | .black-divider {
46 | border: 0;
47 | border-top: 1px solid $theme-dark;
48 | height: 1px;
49 | margin: 0;
50 | margin-top: 1em;
51 | }
52 |
--------------------------------------------------------------------------------
/src/styles/triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uclaacm/TeachLAFrontend/fe30777a0ea6956a081985ad585eb507c02ac429/src/styles/triangle.png
--------------------------------------------------------------------------------
/src/util/classes.js:
--------------------------------------------------------------------------------
1 | const getInstructorString = (instructors, userData, def = '') => {
2 | let instString = '';
3 | if (!userData) return def;
4 |
5 | switch (instructors.length) {
6 | case 0:
7 | break;
8 | case 1:
9 | instString = (userData[instructors[0]] || {}).displayName;
10 | break;
11 | case 2:
12 | instString = instructors.map((id) => (userData[id] || {}).displayName).join(' and ');
13 | break;
14 | default:
15 | instString = instructors.map((id) => (userData[id] || {}).displayName).join(', ');
16 | }
17 |
18 | return instString;
19 | };
20 |
21 | export { getInstructorString };
22 |
--------------------------------------------------------------------------------
/src/util/languages/CodeDownloader.js:
--------------------------------------------------------------------------------
1 | export default class CodeDownloader {
2 | static download = (name, language, code) => {
3 | const extension = `.${language.extension || 'txt'}`;
4 |
5 | // taken from this: https://stackoverflow.com/questions/44656610/download-a-string-as-txt-file-in-react
6 | const element = document.createElement('a');
7 | const file = new Blob([language.renderDownload(code, true)], { type: 'text/plain' });
8 | element.href = URL.createObjectURL(file);
9 | element.download = name + extension;
10 | document.body.appendChild(element); // Required for this to work in FireFox
11 | element.click();
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/src/util/languages/JsSourceDocLoggingScript.js:
--------------------------------------------------------------------------------
1 | export default function getJsSrcDocLoggingScript() {
2 | return `
3 |
37 | `;
38 | }
39 |
--------------------------------------------------------------------------------
/src/util/languages/Processing.js:
--------------------------------------------------------------------------------
1 | import getJsSrcDocLoggingScript from './JsSourceDocLoggingScript';
2 |
3 | const getUserScript = (code) => `
4 |
7 | `;
8 |
9 | const getProcessingSrcDocBody = (code, showConsole) => `
10 |
11 | ${
12 | showConsole
13 | ? ''
14 | : ''
15 | }
16 | ${getJsSrcDocLoggingScript()}
17 | ${getUserScript(code)}
18 |
19 | `;
20 |
21 | const getProcessingSrcDocHead = () => `
22 |
23 |
24 |
25 |
26 |
27 |
46 |
47 | `;
48 |
49 | export default function CreateProcessingDoc(code, showConsole) {
50 | return ` ${getProcessingSrcDocHead()} ${getProcessingSrcDocBody(code, showConsole)}`;
51 | }
52 |
--------------------------------------------------------------------------------
/src/util/languages/Python.js:
--------------------------------------------------------------------------------
1 | import Sk from 'skulpt';
2 |
3 | export default function CreatePythonDoc(prog) {
4 | function outf(text) {
5 | const mypre = document.getElementById('inner');
6 | let received;
7 | if (text !== '\\n') {
8 | received = true;
9 | } else if (received) {
10 | received = false;
11 | } else {
12 | received = true;
13 | }
14 | if (received) {
15 | mypre.value += `> ${text}`;
16 | }
17 | if (mypre.scrollTop >= mypre.scrollHeight - mypre.offsetHeight - mypre.offsetHeight) {
18 | mypre.scrollTop = mypre.scrollHeight;
19 | }
20 | }
21 |
22 | function builtinRead(x) {
23 | if (Sk.builtinFiles === undefined || Sk.builtinFiles.files[x] === undefined) {
24 | throw Error(`File not found: '${x}'`);
25 | }
26 | return Sk.builtinFiles.files[x];
27 | }
28 | Sk.pre = 'output';
29 | Sk.configure({ output: outf, read: builtinRead, __future__: Sk.python3 });
30 | (Sk.TurtleGraphics || (Sk.TurtleGraphics = {})).target = 'my-canvas';
31 | Sk.misceval
32 | .asyncToPromise(() => Sk.importMainWithBody('', false, prog, true))
33 | .catch((err) => {
34 | const b = document.getElementById('output');
35 | if (b) {
36 | b.style.display = 'block';
37 | }
38 | const a = document.getElementById('inner');
39 | a.value += `\nERROR: ${err.toString()}`;
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/src/util/languages/React.js:
--------------------------------------------------------------------------------
1 | import getJsSrcDocLoggingScript from './JsSourceDocLoggingScript';
2 |
3 | const getReactSrcDocHead = () => `
4 |
5 |
6 |
7 |
8 |
9 |
10 |
35 |
36 | `;
37 |
38 | const getUserScript = (code) => `
39 |
46 | `;
47 |
48 | const getReactSrcDocBody = (code, showConsole) => `
49 |
50 | ${
51 | showConsole
52 | ? ''
53 | : ''
54 | }
55 | ${getJsSrcDocLoggingScript()}
56 | ${getUserScript(code)}
57 |
58 |
59 | `;
60 |
61 | export default function CreateReactDoc(code, showConsole) {
62 | return ` ${getReactSrcDocHead()} ${getReactSrcDocBody(code, showConsole)}`;
63 | }
64 |
--------------------------------------------------------------------------------
/src/util/languages/languages.js:
--------------------------------------------------------------------------------
1 | import { faPython, faHtml5, faReact } from '@fortawesome/free-brands-svg-icons';
2 | import { faCogs } from '@fortawesome/free-solid-svg-icons';
3 | import CreateProcessingDoc from './Processing';
4 | import CreatePythonDoc from './Python';
5 | import CreateReactDoc from './React';
6 |
7 | export const SUPPORTED_LANGUAGES = [
8 | {
9 | value: 'python',
10 | display: 'Python',
11 | icon: faPython,
12 | codemirror: 'python',
13 | extension: 'py',
14 | render: CreatePythonDoc,
15 | },
16 | // {
17 | // value: "javascript",
18 | // display: "JavaScript",
19 | // icon: faJs,
20 | // codemirror: "javascript",
21 | // extension: "js",
22 | // },
23 | {
24 | value: 'html',
25 | display: 'HTML',
26 | icon: faHtml5,
27 | codemirror: 'htmlmixed',
28 | extension: 'html',
29 | },
30 | {
31 | value: 'processing',
32 | display: 'Processing',
33 | icon: faCogs,
34 | codemirror: 'javascript',
35 | extension: 'html',
36 | render: CreateProcessingDoc,
37 | renderDownload: CreateProcessingDoc,
38 | },
39 | {
40 | value: 'react',
41 | display: 'React.JS',
42 | icon: faReact,
43 | codemirror: 'jsx',
44 | extension: 'html',
45 | render: CreateReactDoc,
46 | renderDownload: CreateReactDoc,
47 | },
48 | ];
49 |
50 | const DEFAULT_LANGUAGE = {
51 | identifier: 'txt',
52 | display: 'Text',
53 | codemirror: undefined,
54 | extension: 'txt',
55 | render: (code) => code,
56 | renderDownload: (code) => code,
57 | };
58 |
59 | export const getLanguageData = (lang) => ({
60 | ...DEFAULT_LANGUAGE,
61 | ...SUPPORTED_LANGUAGES.find((data) => data.value === lang),
62 | });
63 |
64 | export const enrichWithLanguageData = (arr) => arr.map((sketch) => ({
65 | ...sketch,
66 | language: getLanguageData(sketch.language),
67 | }));
68 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "types": ["vite/client"],
5 | "jsx": "react-jsx",
6 | "module": "esnext",
7 | "esModuleInterop": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "strict": true,
10 | "skipLibCheck": true,
11 | "lib": [
12 | "dom",
13 | "dom.iterable",
14 | "esnext"
15 | ],
16 | "allowJs": true,
17 | "allowSyntheticDefaultImports": true,
18 | "noFallthroughCasesInSwitch": true,
19 | "moduleResolution": "node",
20 | "resolveJsonModule": true,
21 | "isolatedModules": true,
22 | "noEmit": true
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react';
3 |
4 | export default defineConfig(() => {
5 | return {
6 | build: {
7 | outDir: 'build',
8 | },
9 | plugins: [react()],
10 | };
11 | });
12 |
--------------------------------------------------------------------------------