diff --git a/src/components/Output/Output.js b/src/components/Output/Output.js index acc3913e..402f7a35 100644 --- a/src/components/Output/Output.js +++ b/src/components/Output/Output.js @@ -1,9 +1,10 @@ import React from "react"; -import { PYTHON, HTML, PROCESSING } from "../../constants"; +import { PYTHON, HTML, PROCESSING, REACT } from "../../constants"; import { OUTPUT_ONLY } from "../../constants"; import EditorRadio from "../TextEditor/components/EditorRadio.js"; import CreateProcessingDoc from "../Output/Processing"; import CreatePythonDoc from "../Output/Python"; +import CreateReactDoc from "../Output/React"; import { Button } from "reactstrap"; import ViewportAwareButton from "../common/ViewportAwareButton.js"; import OpenPanelButtonContainer from "../common/containers/OpenPanelButtonContainer.js"; @@ -99,6 +100,9 @@ class Output extends React.Component { case PROCESSING: srcDocFunc = () => CreateProcessingDoc(runResult, showConsole); break; + case REACT: + srcDocFunc = () => CreateReactDoc(runResult, showConsole); + break; case PYTHON: runResult = btoa(runResult); srcDocFunc = () => CreatePythonDoc(runResult, showConsole); diff --git a/src/components/Output/Processing.js b/src/components/Output/Processing.js index 90857b4d..3d8628da 100644 --- a/src/components/Output/Processing.js +++ b/src/components/Output/Processing.js @@ -1,39 +1,4 @@ -const getProcessingSrcDocLoggingScript = () => ` - - `; +import { getJsSrcDocLoggingScript } from "./constants"; const getUserScript = code => ` + + + + + + `; + +const getUserScript = (code) => ` + +`; + +const getReactSrcDocBody = (code, showConsole) => ` + + ${ + showConsole + ? `
` + : `` + } + ${getJsSrcDocLoggingScript()} + ${getUserScript(code)} +
+ + `; + +export default function (code, showConsole) { + return ` ${getReactSrcDocHead()} ${getReactSrcDocBody(code, showConsole)}`; +} diff --git a/src/components/Output/constants/index.js b/src/components/Output/constants/index.js new file mode 100644 index 00000000..59342ec7 --- /dev/null +++ b/src/components/Output/constants/index.js @@ -0,0 +1,36 @@ +export const getJsSrcDocLoggingScript = () => ` + +`; diff --git a/src/components/Sketches/components/ConfirmDeleteModal.js b/src/components/Sketches/components/ConfirmDeleteModal.js index 29870508..66997de8 100644 --- a/src/components/Sketches/components/ConfirmDeleteModal.js +++ b/src/components/Sketches/components/ConfirmDeleteModal.js @@ -20,13 +20,10 @@ class ConfirmDeleteModal extends React.Component { fetch .deleteSketch(data) .then((res) => { - return res.json(); - }) - .then((json) => { - if (!json.ok) { + if (!res.ok) { this.setState({ spinner: false, - error: json.error || "Failed to create sketch, please try again later", + error: res.text() || "Failed to delete sketch, please try again later", }); return; } diff --git a/src/components/Sketches/components/CreateSketchModal.js b/src/components/Sketches/components/CreateSketchModal.js index defc34c4..d8d19752 100644 --- a/src/components/Sketches/components/CreateSketchModal.js +++ b/src/components/Sketches/components/CreateSketchModal.js @@ -1,6 +1,6 @@ import React from "react"; import DropdownButton from "./DropdownButton"; -import ImageSelector from "../../common/ImageSelector" +import ImageSelector from "../../common/ImageSelector"; import { SketchThumbnailArray, LanguageDropdownValues, @@ -46,7 +46,7 @@ class CreateSketchModal extends React.Component { }); }; - setNext = val => { + setNext = (val) => { this.setState({ next: val, error: "", @@ -111,7 +111,7 @@ class CreateSketchModal extends React.Component { return false; }; - onFirstSubmit = e => { + onFirstSubmit = (e) => { e.preventDefault(); if (this.badNameInput() || this.badLanguageInput()) { return; @@ -119,7 +119,7 @@ class CreateSketchModal extends React.Component { this.setNext(true); }; - onSecondSubmit = async e => { + onSecondSubmit = async (e) => { e.preventDefault(); if (this.badThumbnailInput()) return; @@ -135,23 +135,18 @@ class CreateSketchModal extends React.Component { try { fetch .createSketch(data) - .then(res => { + .then((res) => { + if (!res.ok) throw new Error(`Failed to create user! Got status ${res.status}`); return res.json(); }) - .then(json => { - if (!json.ok) { - this.setState({ - disableSubmit: false, - error: json.error || "Failed to create sketch, please try again later", - }); - return; - } - this.props.addProgram(json.data.key, json.data.programData || {}); - this.props.setMostRecentProgram(json.data.key); + .then((json) => { + const { uid, ...programData } = json; + this.props.addProgram(uid, programData || {}); + this.props.setMostRecentProgram(uid); this.setState({ redirect: true }); this.closeModal(); }) - .catch(err => { + .catch((err) => { this.setState({ disableSubmit: false, error: "Failed to create sketch, please try again later", @@ -245,7 +240,7 @@ class CreateSketchModal extends React.Component { this.setState({ name: e.target.value })} + onChange={(e) => this.setState({ name: e.target.value })} value={this.state.name} id="sketch-name" /> @@ -259,7 +254,7 @@ class CreateSketchModal extends React.Component { this.setState({ language: lang })} + onSelect={(lang) => this.setState({ language: lang })} displayValue={this.state.language.display || LanguageDropdownDefault.display} /> diff --git a/src/components/Sketches/components/EditSketchModal.js b/src/components/Sketches/components/EditSketchModal.js index 749aa91e..2777928e 100644 --- a/src/components/Sketches/components/EditSketchModal.js +++ b/src/components/Sketches/components/EditSketchModal.js @@ -80,7 +80,7 @@ class EditSketchModal extends React.Component { return false; }; - handleSubmitEdit = async e => { + handleSubmitEdit = async (e) => { e.preventDefault(); if (this.badNameInput() || this.badLanguageInput()) { @@ -109,29 +109,27 @@ class EditSketchModal extends React.Component { try { fetch .updatePrograms(this.props.uid, updateData) - .then(res => { - return res.json(); - }) - .then(json => { - if (!json.ok) { + .then((res) => { + if (res.ok) { + if (this.state.newLanguage !== -1) { + this.props.setProgramLanguage(this.props.sketchKey, this.state.newLanguage.value); + } + if (this.state.newName !== -1) { + this.props.setProgramName(this.props.sketchKey, this.state.newName); + } + if (this.state.newThumbnail !== -1) { + this.props.setProgramThumbnail(this.props.sketchKey, this.state.newThumbnail); + } + this.closeModal(); + } else { this.setState({ disableSubmit: false, - error: json.error || "Failed to edit sketch, please try again later", + error: res.text() || "Failed to edit sketch, please try again later", }); return; } - if (this.state.newLanguage !== -1) { - this.props.setProgramLanguage(this.props.sketchKey, this.state.newLanguage.value); - } - if (this.state.newName !== -1) { - this.props.setProgramName(this.props.sketchKey, this.state.newName); - } - if (this.state.newThumbnail !== -1) { - this.props.setProgramThumbnail(this.props.sketchKey, this.state.newThumbnail); - } - this.closeModal(); }) - .catch(err => { + .catch((err) => { this.setState({ disableSubmit: false, error: "Failed to edit sketch, please try again later", @@ -176,7 +174,7 @@ class EditSketchModal extends React.Component { this.setState({ newName: e.target.value })} + onChange={(e) => this.setState({ newName: e.target.value })} value={this.state.newName !== -1 ? this.state.newName : this.props.sketchName} id="sketch-name" /> @@ -190,7 +188,7 @@ class EditSketchModal extends React.Component { this.setState({ newLanguage: lang })} + onSelect={(lang) => this.setState({ newLanguage: lang })} displayValue={ this.state.newLanguage !== -1 ? this.state.newLanguage.display @@ -289,7 +287,7 @@ class EditSketchModal extends React.Component { /> ); return ( - { + if (!res.ok) throw new Error(`Failed to create sketch! Got status ${res.status}.`); return res.json(); }) .then((json) => { - if (!json.ok) { - this.setState({ - error: json.error || "Failed to create sketch, please try again later", - }); - return; - } + const { uid, ...programData } = json; this.setState({ forking: false, forked: true }); - this.props.addProgram(json.data.key, json.data.programData || {}); + this.props.addProgram(uid, programData || {}); }) .catch((err) => { this.setState({ diff --git a/src/components/common/DropdownButton.js b/src/components/common/DropdownButton.js index f640e4fd..537b6a0a 100644 --- a/src/components/common/DropdownButton.js +++ b/src/components/common/DropdownButton.js @@ -1,8 +1,8 @@ import React from "react"; import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from "reactstrap"; import { faCogs } from "@fortawesome/free-solid-svg-icons"; -import { faPython } from "@fortawesome/free-brands-svg-icons"; -import { faHtml5 } from "@fortawesome/free-brands-svg-icons"; +import { faPython, faHtml5, faReact } from "@fortawesome/free-brands-svg-icons"; +import { PYTHON, PROCESSING, REACT, HTML } from "../../constants"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; /**--------Props--------------- @@ -25,11 +25,11 @@ export default class DropdownButton extends React.Component { //==============React Lifecycle Functions===================// componentDidMount() {} - toggleHandler = prevVal => { + toggleHandler = (prevVal) => { this.setState({ dropdownOpen: !prevVal }); }; - selectLanguage = program => { + selectLanguage = (program) => { let result = true; if (this.props.dirty) { result = window.confirm("Are you sure you want to change programs? You have unsaved changes"); @@ -42,16 +42,19 @@ export default class DropdownButton extends React.Component { renderDropdownItems = () => { //map each program string in the array to a dropdown item - return this.props.dropdownItems.map(program => { + return this.props.dropdownItems.map((program) => { let faLanguage; switch (program.language) { - case "python": + case PYTHON: faLanguage = faPython; break; - case "processing": + case PROCESSING: faLanguage = faCogs; break; - case "html": + case REACT: + faLanguage = faReact; + break; + case HTML: default: faLanguage = faHtml5; } @@ -72,13 +75,16 @@ export default class DropdownButton extends React.Component { let faLanguage; switch (this.props.currentLanguage) { - case "python": + case PYTHON: faLanguage = faPython; break; - case "processing": + case PROCESSING: faLanguage = faCogs; break; - case "html": + case REACT: + faLanguage = faReact; + break; + case HTML: default: faLanguage = faHtml5; } diff --git a/src/constants/index.js b/src/constants/index.js index 24de4654..58ffeaf9 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -4,8 +4,9 @@ const PYTHON = "python"; const JAVASCRIPT = "javascript"; const HTML = "html"; const PROCESSING = "processing"; +const REACT = "react"; -const SUPPORTED_LANGUAGES = [PYTHON, JAVASCRIPT, HTML, PROCESSING]; +const SUPPORTED_LANGUAGES = [PYTHON, JAVASCRIPT, HTML, PROCESSING, REACT]; //used for syntax highlighting in editor let CODEMIRROR_CONVERSIONS = {}; @@ -21,6 +22,8 @@ SUPPORTED_LANGUAGES.forEach((lang) => { return (CODEMIRROR_CONVERSIONS[lang] = "htmlmixed"); case PROCESSING: return (CODEMIRROR_CONVERSIONS[lang] = "javascript"); + case REACT: + return (CODEMIRROR_CONVERSIONS[lang] = "jsx"); default: console.error("SUPPORTED LANGUAGE WITH NO MODE"); } @@ -51,10 +54,10 @@ const ROUTER_BASE_NAME = "/"; var SERVER_URL = "http://localhost:8081"; if (process && process.env) { if (process.env.REACT_APP_SERVER_TYPE === "staging") { - SERVER_URL = "https://teach-la-staging-backend.herokuapp.com"; + SERVER_URL = "https://tla-backend-staging.herokuapp.com"; } if (process.env.REACT_APP_SERVER_TYPE === "prod") { - SERVER_URL = "https://teach-la-backend.herokuapp.com"; + SERVER_URL = "https://tla-backend-prod.herokuapp.com"; } } @@ -76,6 +79,7 @@ module.exports = { JAVASCRIPT, HTML, PROCESSING, + REACT, // photo names PHOTO_NAMES, diff --git a/src/lib/fetch.js b/src/lib/fetch.js index 3d9b3e62..16d4871f 100644 --- a/src/lib/fetch.js +++ b/src/lib/fetch.js @@ -16,7 +16,7 @@ import constants from "../constants"; */ export const getUserData = async (uid = "", includePrograms = false) => { const getUserDataEndpoint = (uid = "", includePrograms = false) => - `${constants.SERVER_URL}/getUserData/${uid}${includePrograms ? "?programs=true" : ""}`; + `${constants.SERVER_URL}/user/get?uid=${uid}${includePrograms ? "&programs=true" : ""}`; const options = { method: "get", @@ -24,12 +24,19 @@ export const getUserData = async (uid = "", includePrograms = false) => { }; try { - let result = await fetch(getUserDataEndpoint(uid, includePrograms), options); - let { ok, data, error } = await result.json(); - + const result = await fetch(getUserDataEndpoint(uid, includePrograms), options); + const status = await result.status; + const ok = status === 200; + if (status === 404) { + await createUser(uid); + return getUserData(uid, includePrograms); + } + let data = ok ? await result.json() : {}; + let error = !ok ? await result.text() : ""; return { ok, data, error }; } catch (err) { - return { ok: "false", error: "SERVER ERROR: Unable to get user data from server", err: err }; + await createUser(uid); + return getUserData(uid, includePrograms); } }; @@ -48,7 +55,7 @@ const makeServerRequest = (data, endpoint, method = "post") => { }, }; - if (method === "post" || method === "put") { + if (method !== "get") { let body = ""; // if the passed-in data object has at least 1 key, set the body to the stringified data object try { @@ -72,8 +79,13 @@ const makeServerRequest = (data, endpoint, method = "post") => { */ export const updatePrograms = (uid = "", programs) => { - const endpoint = `updatePrograms/${uid}`; - return makeServerRequest(programs, endpoint, "put"); + const endpoint = `program/update`; + return makeServerRequest({ uid, programs }, endpoint, "put"); +}; + +export const createUser = (uid) => { + console.log("creating user"); + return makeServerRequest({ uid }, "user/create", "post"); }; /** @@ -83,8 +95,8 @@ export const updatePrograms = (uid = "", programs) => { */ export const updateUserData = (uid = "", userData) => { - const endpoint = `updateUserData/${uid}`; - return makeServerRequest(userData, endpoint); + const endpoint = `user/update`; + return makeServerRequest({ uid, ...userData }, endpoint, "put"); }; /** @@ -92,8 +104,9 @@ export const updateUserData = (uid = "", userData) => { * @param {Object} data required data to create program - might eventually become enumerated */ -export const createSketch = data => { - return makeServerRequest(data, "createProgram"); +export const createSketch = (data) => { + const { uid, ...rest } = data; + return makeServerRequest({ uid, program: rest }, "program/create"); }; /** @@ -101,8 +114,9 @@ export const createSketch = data => { * @param {Object} data required data to delete program (uid, docID, name) */ -export const deleteSketch = data => { - return makeServerRequest(data, "deleteProgram"); +export const deleteSketch = (data) => { + const { uid, name } = data; + return makeServerRequest({ uid, pid: name }, "program/delete", "delete"); }; /** @@ -110,9 +124,10 @@ export const deleteSketch = data => { * @param {string} docID the key for the requested program in the top-level programs object */ -export const getSketch = async docID => { - const endpoint = `getProgram/${docID}`; +export const getSketch = async (docID) => { + const endpoint = `program/get?pid=${docID}`; let result = await makeServerRequest({}, endpoint, "get"); - let { ok, sketch } = await result.json(); + let ok = await result.ok; + let sketch = await result.json(); return { ok, sketch }; }; diff --git a/src/util/languages/CodeDownloader.js b/src/util/languages/CodeDownloader.js index 75afe25e..027df363 100644 --- a/src/util/languages/CodeDownloader.js +++ b/src/util/languages/CodeDownloader.js @@ -1,14 +1,17 @@ import ProcessingConstructor from "../../components/Output/Processing"; +import ReactConstructor from "../../components/Output/React"; +import { PYTHON, REACT, HTML, PROCESSING } from "../../constants"; export default class CodeDownloader { static download = (name, language, code) => { let extension = "."; switch (language) { - case "python": + case PYTHON: extension += "py"; break; - case "processing": // this is because we construct the processing result as an HTML file. jank. - case "html": + case PROCESSING: // this is because we construct the processing result as an HTML file. jank. + case REACT: // same here + case HTML: extension += "html"; break; default: @@ -17,10 +20,15 @@ export default class CodeDownloader { // taken from this: https://stackoverflow.com/questions/44656610/download-a-string-as-txt-file-in-react const element = document.createElement("a"); let file; - if (language === "processing") { - file = new Blob([ProcessingConstructor(code, true)], { type: "text/plain" }); - } else { - file = new Blob([code], { type: "text/plain" }); + switch (language) { + case PROCESSING: + file = new Blob([ProcessingConstructor(code, true)], { type: "text/plain" }); + break; + case REACT: + file = new Blob([ReactConstructor(code, true)], { type: "text/plain" }); + break; + default: + file = new Blob([code], { type: "text/plain" }); } element.href = URL.createObjectURL(file); element.download = name + extension;