diff --git a/package-lock.json b/package-lock.json index e83e5fbb..2e9cf5c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4705,9 +4705,9 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==" + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.2.tgz", + "integrity": "sha512-hIE5dXkRzRvnZ5vhkRfQxUvDxQZmD9oueA08jDYRBKJHx+VIl/Pne/e0A4x9LObEEthC/TqiZybUoNM4tRgnKg==" }, "core-js-compat": { "version": "3.6.4", @@ -7120,9 +7120,9 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", "requires": { "websocket-driver": ">=0.5.1" } @@ -16618,6 +16618,16 @@ "requires": { "faye-websocket": "^0.10.0", "uuid": "^3.0.1" + }, + "dependencies": { + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "requires": { + "websocket-driver": ">=0.5.1" + } + } } }, "sockjs-client": { @@ -16640,14 +16650,6 @@ "requires": { "ms": "^2.1.1" } - }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "requires": { - "websocket-driver": ">=0.5.1" - } } } }, diff --git a/src/actions/outputActions.js b/src/actions/outputActions.js index 00486b18..45eee7d8 100644 --- a/src/actions/outputActions.js +++ b/src/actions/outputActions.js @@ -3,21 +3,21 @@ export function clearOutput() { return { type: CLEAR_OUTPUT }; } -export const SET_RUN_RESULT = 'SET_RUN_RESULT' -export function setRunResult(value){ - return {type: SET_RUN_RESULT, value} +export const SET_RUN_RESULT = "SET_RUN_RESULT"; +export function setRunResult(value) { + return { type: SET_RUN_RESULT, value }; } -export const SET_OUTPUT_LANGUAGE = 'SET_OUTPUT_LANGAUGE' -export function setOutputLanguage(value){ - return {type: SET_OUTPUT_LANGUAGE, value} +export const SET_OUTPUT_LANGUAGE = "SET_OUTPUT_LANGAUGE"; +export function setOutputLanguage(value) { + return { type: SET_OUTPUT_LANGUAGE, value }; } -export const SET_OUTPUT = 'SET_OUTPUT' -export function setOutput({output}){ +export const SET_OUTPUT = "SET_OUTPUT"; +export function setOutput({ output }) { //if output is not an object - if(!output){ - return {type:"IGNORE"} + if (!output) { + return { type: "IGNORE" }; } - return {type:SET_OUTPUT, runResult:output.code, language: output.language} -} \ No newline at end of file + return { type: SET_OUTPUT, runResult: output.code, language: output.language }; +} diff --git a/src/components/EditorAndOutput/EditorAndOutput.js b/src/components/EditorAndOutput/EditorAndOutput.js new file mode 100644 index 00000000..e3aff3ad --- /dev/null +++ b/src/components/EditorAndOutput/EditorAndOutput.js @@ -0,0 +1,101 @@ +import React from "react"; +import SplitPane from "react-split-pane"; +import OutputContainer from "../Output/OutputContainer.js"; +import TextEditorContainer from "../TextEditor/containers/TextEditorContainer.js"; + +import { EDITOR_WIDTH_BREAKPOINT, CODE_ONLY, OUTPUT_ONLY, PANEL_SIZE } from "../../constants"; +import CodeDownloader from "../../util/languages/CodeDownloader"; + +import "codemirror/lib/codemirror.css"; +import "codemirror/theme/material.css"; +import "codemirror/theme/duotone-light.css"; +import "styles/CustomCM.scss"; +import "styles/Resizer.scss"; +import "styles/Editor.scss"; + +class EditorAndOutput extends React.Component { + handleDownload = () => { + CodeDownloader.download(this.props.sketchName, this.props.language, this.props.code); + }; + + renderCodeAndOutput = () => ( + this.props.changePane1Style({ pane1Style: {} })} + onDragFinished={() => + this.props.changePane1Style({ pane1Style: { transition: "width .5s ease" } }) + } + split="vertical" //the resizer is a vertical line (horizontal means resizer is a horizontal bar) + minSize={ + (this.props.panelOpen ? this.props.screenWidth - PANEL_SIZE : this.props.screenWidth) * 0.33 + } //minimum size of code is 33% of the remaining screen size + maxSize={ + (this.props.panelOpen ? this.props.screenWidth - PANEL_SIZE : this.props.screenWidth) * 0.75 + } //max size of code is 75% of the remaining screen size + size={ + ((this.props.panelOpen ? this.props.screenWidth - PANEL_SIZE : this.props.screenWidth) / + 5) * + 3 + } //the initial size of the text editor section + allowResize={true} + > + {this.renderCode()} + {this.renderOutput()} + + ); + + renderCode = () => ( + + ); + + renderOutput = () => ( + + ); + render = () => { + const codeStyle = { + width: this.props.screenWidth - (this.props.left || 0), + height: this.props.screenHeight, + }; + + switch (this.props.viewMode) { + case CODE_ONLY: + return
{this.renderCode()}
; + case OUTPUT_ONLY: + return
{this.renderOutput()}
; + default: + return this.renderCodeAndOutput(); + } + }; +} + +export default EditorAndOutput; diff --git a/src/components/Main.js b/src/components/Main.js index 596fbb64..e0406217 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -1,24 +1,16 @@ import React from "react"; import { Redirect } from "react-router-dom"; -import SplitPane from "react-split-pane"; -import OutputContainer from "./Output/OutputContainer.js"; -import TextEditorContainer from "./TextEditor/containers/TextEditorContainer.js"; + import * as fetch from "../lib/fetch.js"; import * as cookies from "../lib/cookies.js"; -import SketchesPageContainer from "./Sketches/containers/SketchesContainer"; -import "styles/Main.scss"; + import ProfilePanelContainer from "./common/containers/ProfilePanelContainer"; +import SketchesPageContainer from "./Sketches/containers/SketchesContainer"; +import EditorAndOutput from "./EditorAndOutput/EditorAndOutput"; -import { EDITOR_WIDTH_BREAKPOINT, CODE_AND_OUTPUT, CODE_ONLY, OUTPUT_ONLY } from "../constants"; -import CodeDownloader from "../util/languages/CodeDownloader"; +import { EDITOR_WIDTH_BREAKPOINT, CODE_AND_OUTPUT, CODE_ONLY } from "../constants"; -import { PANEL_SIZE } from "../constants"; -import "codemirror/lib/codemirror.css"; -import "codemirror/theme/material.css"; -import "codemirror/theme/duotone-light.css"; -import "styles/CustomCM.scss"; -import "styles/Resizer.scss"; -import "styles/Editor.scss"; +import "styles/Main.scss"; /**------Props------- * togglePanel: function to call when you want the Profile Panel to disappear/reapper @@ -39,7 +31,6 @@ class Main extends React.Component { this.props.setTheme(cookies.getThemeFromCookie()); } - //==============React Lifecycle Functions Start===================// componentDidUpdate(prevProps) { if (this.props.screenWidth !== prevProps.screenWidth) { if (this.props.screenWidth <= EDITOR_WIDTH_BREAKPOINT) { @@ -84,10 +75,6 @@ class Main extends React.Component { this.props.cleanCode(this.props.mostRecentProgram); // Set code's "dirty" state to false }; - handleDownload = () => { - CodeDownloader.download(this.props.name, this.props.language, this.props.code); - }; - renderContent = () => { switch (this.props.contentType) { case "editor": @@ -98,80 +85,35 @@ class Main extends React.Component { } }; - renderCodeAndOutput = () => ( - this.setState({ pane1Style: {} })} - onDragFinished={() => this.setState({ pane1Style: { transition: "width .5s ease" } })} - split="vertical" //the resizer is a vertical line (horizontal means resizer is a horizontal bar) - minSize={ - (this.props.panelOpen ? this.props.screenWidth - PANEL_SIZE : this.props.screenWidth) * 0.33 - } //minimum size of code is 33% of the remaining screen size - maxSize={ - (this.props.panelOpen ? this.props.screenWidth - PANEL_SIZE : this.props.screenWidth) * 0.75 - } //max size of code is 75% of the remaining screen size - size={ - ((this.props.panelOpen ? this.props.screenWidth - PANEL_SIZE : this.props.screenWidth) / - 5) * - 3 - } //the initial size of the text editor section - allowResize={true} - > - {this.renderCode()} - {this.renderOutput()} - - ); - - updateViewMode = viewMode => { - this.setState({ viewMode }); - }; - - renderCode = () => ( - ( + this.setState({ viewMode })} + // theme theme={this.props.theme} + // sizing + left={this.props.left} + screenWidth={this.props.screenWidth} + screenHeight={this.props.screenHeight} + // view only trigger + viewOnly={false} + // pane + panelOpen={this.props.panelOpen} + pane1Style={this.state.pane1Style} + changePane1Style={newStyle => this.setState(newStyle)} + // program information + mostRecentProgram={this.props.mostRecentProgram} + language={this.state.language} + code={this.state.code} + programid={this.props.programid} + sketchName={this.state.sketchName} + // save handler + saveText={this.state.saveText} + handleSave={this.handleSave} /> ); - renderOutput = () => ( - - ); - - renderEditor = () => { - const codeStyle = { - width: this.props.screenWidth - (this.props.left || 0), - height: this.props.screenHeight, - }; - - switch (this.state.viewMode) { - case CODE_ONLY: - return
{this.renderCode()}
; - case OUTPUT_ONLY: - return
{this.renderOutput()}
; - default: - return this.renderCodeAndOutput(); - } - }; - render() { // this stops us from rendering editor with no sketches available if (this.props.contentType === "editor" && this.props.listOfPrograms.length === 0) { diff --git a/src/components/Output/Output.js b/src/components/Output/Output.js index 612b92af..acc3913e 100644 --- a/src/components/Output/Output.js +++ b/src/components/Output/Output.js @@ -80,7 +80,8 @@ class Output extends React.Component { }; renderOutput = () => { - let { language, runResult } = this.props; + let language = this.props.viewOnly ? this.props.vLanguage : this.props.language; + let runResult = this.props.viewOnly ? this.props.code : this.props.runResult; const { showConsole } = this.state; if (this.firstLoad) { diff --git a/src/components/TextEditor/components/TextEditor.js b/src/components/TextEditor/components/TextEditor.js index 5db33e7b..6381cce6 100644 --- a/src/components/TextEditor/components/TextEditor.js +++ b/src/components/TextEditor/components/TextEditor.js @@ -33,10 +33,12 @@ class TextEditor extends React.Component { this.state = { codeMirrorInstance: null, currentLine: 0, + sketch: null, }; } //==============React Lifecycle Functions===================// + componentDidMount() { window.addEventListener("beforeunload", this.onLeave); window.addEventListener("close", this.onLeave); @@ -113,6 +115,8 @@ class TextEditor extends React.Component { }; renderDropdown = () => ; + renderSketchName = () =>
{this.props.sketchName}
; + renderBanner = () => { let thumbnail = SketchThumbnailArray[this.props.thumbnail]; return ( @@ -123,7 +127,7 @@ class TextEditor extends React.Component { src={`${process.env.PUBLIC_URL}/img/sketch-thumbnails/${thumbnail}.svg`} alt="sketch thumbnail" /> - {this.renderDropdown()} + {this.props.viewOnly ? this.renderSketchName() : this.renderDropdown()}
- } - text={this.props.saveText} - /> + {this.props.viewOnly ? null : ( + } + text={this.props.saveText} + /> + )} - + { + + } ); }; @@ -159,31 +167,33 @@ class TextEditor extends React.Component { }; return ( -
- {this.renderBanner()} -
- { - codeMirrorInstance.refresh(); - this.setCodeMirrorInstance(codeMirrorInstance); - }} - value={this.props.code} - lineWrapping - indentWithTabs={true} - options={options} - onCursor={cm => { - this.setCurrentLine(cm); +
+
+ {this.renderBanner()} +
+ > + { + codeMirrorInstance.refresh(); + this.setCodeMirrorInstance(codeMirrorInstance); + }} + value={this.props.code} + lineWrapping + indentWithTabs={true} + options={options} + onCursor={cm => { + this.setCurrentLine(cm); + }} + onBeforeChange={this.updateCode} + onChange={this.updateCode} + /> +
); diff --git a/src/components/TextEditor/containers/TextEditorContainer.js b/src/components/TextEditor/containers/TextEditorContainer.js index 024e4b21..595badfc 100644 --- a/src/components/TextEditor/containers/TextEditorContainer.js +++ b/src/components/TextEditor/containers/TextEditorContainer.js @@ -10,7 +10,6 @@ const mapStateToProps = (state, ownProps) => { //should have 2 keys, code (which is the code) and langauge (which is the language the code is written it) // add key dirty const programData = state.programs.get(mostRecentProgram, Immutable.Map()).toJS(); - return { ...programData, mostRecentProgram, diff --git a/src/components/ViewOnly.js b/src/components/ViewOnly.js new file mode 100644 index 00000000..e1112e2c --- /dev/null +++ b/src/components/ViewOnly.js @@ -0,0 +1,149 @@ +import React from "react"; + +import * as fetch from "../lib/fetch.js"; +import * as cookies from "../lib/cookies.js"; + +import ProfilePanelContainer from "./common/containers/ProfilePanelContainer"; +import EditorAndOutput from "./EditorAndOutput/EditorAndOutput"; +import PageNotFound from "./PageNotFound"; +import LoadingPage from "./common/LoadingPage"; + +import { EDITOR_WIDTH_BREAKPOINT, CODE_AND_OUTPUT, CODE_ONLY } from "../constants"; + +import "styles/Main.scss"; + +/**------Props------- + * togglePanel: function to call when you want the Profile Panel to disappear/reapper + * panelOpen: boolean telling whether the Profile Panel is open or not + * left: the left css property that should be applied on the top level element + */ + +class ViewOnly extends React.Component { + constructor(props) { + super(props); + this.state = { + viewMode: this.props.screenWidth <= EDITOR_WIDTH_BREAKPOINT ? CODE_ONLY : CODE_AND_OUTPUT, + pane1Style: { transition: "width .5s ease" }, + sketchName: "", + language: "", + thumbnail: "", + code: "", + loaded: false, + notfound: false, + originalCode: "", + }; + this.savePrevProgram = this.props.uid !== ""; + this.props.setTheme(cookies.getThemeFromCookie()); + } + + componentDidMount = async () => { + if (this.savePrevProgram) { + await this.codeSaverHelper(); + } + + const { ok, sketch } = await fetch.getSketch(this.props.programid); + + if (!ok) { + this.setState({ notfound: true }); + return; + } + this.setState({ + sketchName: sketch.name, + language: sketch.language, + code: sketch.code, + thumbnail: sketch.thumbnail, + loaded: true, + }); + this.props.setProgramCode(this.props.mostRecentProgram, sketch.code); + this.props.runCode(sketch.code, sketch.language); + }; + + componentDidUpdate(prevProps) { + if (this.props.screenWidth !== prevProps.screenWidth) { + if (this.props.screenWidth <= EDITOR_WIDTH_BREAKPOINT) { + if (this.state.viewMode === CODE_AND_OUTPUT) { + this.setState({ viewMode: CODE_ONLY }); + } + } + } + } + + componentWillUnmount = () => { + if (this.savePrevProgram) { + this.props.setProgramCode(this.props.mostRecentProgram, this.state.originalCode); + } + }; + + codeSaverHelper = async () => { + const { ok: okOriginal, sketch: original } = await fetch.getSketch( + this.props.mostRecentProgram, + ); + + if (!okOriginal) { + this.setState({ notfound: true }); + return; + } + + this.setState({ + originalCode: original.code, + }); + }; + + onThemeChange = () => { + let newTheme = this.props.theme === "dark" ? "light" : "dark"; + cookies.setThemeCookie(newTheme); + this.props.setTheme(newTheme); + }; + + render() { + if (this.state.notfound) { + return ; + } + if (!this.state.loaded) { + return ; + } + const codeStyle = { + left: this.props.left || 0, + width: this.props.screenWidth - (this.props.left || 0), + height: this.props.screenHeight, + }; + + return ( +
+ +
+ this.setState({ viewMode })} + // theme + theme={this.props.theme} + // sizing + left={this.props.left} + screenWidth={this.props.screenWidth} + screenHeight={this.props.screenHeight} + // view only trigger + viewOnly={true} + // pane + panelOpen={this.props.panelOpen} + pane1Style={this.state.pane1Style} + changePane1Style={newStyle => this.setState(newStyle)} + // program information + mostRecentProgram={this.props.mostRecentProgram} + language={this.state.language} + code={this.state.code} + programid={this.props.programid} + sketchName={this.state.sketchName} + thumbnail={this.state.thumbnail} + /> +
+
+ ); + } +} + +export default ViewOnly; diff --git a/src/components/app.js b/src/components/app.js index 00139a69..81454c70 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -3,6 +3,7 @@ import { BrowserRouter as Router, Route, Redirect, Switch } from "react-router-d import { ROUTER_BASE_NAME } from "../constants"; import LoginPage from "./containers/LoginContainer"; import MainContainer from "./containers/MainContainer"; +import ViewOnlyContainer from "./containers/ViewOnlyContainer"; import LoadingPage from "./common/LoadingPage"; import CreateUserPage from "./CreateUser"; import Error from "./Error"; @@ -75,7 +76,7 @@ class App extends React.Component { return isValidUser ? : ; }; - render() { + render = () => { //if we haven't checked if the user is logged in yet, show a loading screen if (!this.state.checkedAuth) { return ; @@ -154,6 +155,13 @@ class App extends React.Component { ) } /> + {/* Get program endpoint */} + ( + + )} + /> {/* Default error page */} ); - } + }; } export default App; diff --git a/src/components/common/ProfilePanel.js b/src/components/common/ProfilePanel.js index c4e688aa..4c8e64d0 100644 --- a/src/components/common/ProfilePanel.js +++ b/src/components/common/ProfilePanel.js @@ -20,6 +20,7 @@ import { faMoon } from "@fortawesome/free-solid-svg-icons"; import { faSun } from "@fortawesome/free-solid-svg-icons"; import { faTimes } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faUserCircle } from "@fortawesome/free-solid-svg-icons"; import "styles/Panel.scss"; import Switch from "./Switch"; import Footer from "./Footer"; @@ -265,9 +266,8 @@ class ProfilePanel extends React.Component { panelButtons.push(this.renderEditorButton()); break; case "editor": - panelButtons.push(this.renderSketchesButton()); - break; default: + panelButtons.push(this.renderSketchesButton()); break; } @@ -291,6 +291,40 @@ class ProfilePanel extends React.Component { ); }; + renderLoginButton = () => ( + + + Login + + ); + + renderCreateUserButton = () => ( + + {/* */} + Create User + + ); + + renderLoggedOutContent = () => ( +
+
+ {this.renderLoginButton()} + {this.renderCreateUserButton()} +
+ {this.renderThemeSwitch()} +
+ ); + renderContent = () => (
{this.renderPanelImage()} @@ -317,7 +351,7 @@ class ProfilePanel extends React.Component { return (
{this.renderCollapseButton()} - {this.renderContent()} + {this.props.uid ? this.renderContent() : this.renderLoggedOutContent()}
); diff --git a/src/components/common/ProfilePanel.test.js b/src/components/common/ProfilePanel.test.js index 19bf4150..cde93417 100644 --- a/src/components/common/ProfilePanel.test.js +++ b/src/components/common/ProfilePanel.test.js @@ -11,28 +11,28 @@ describe("ProfilePanel", () => { }); it("handles displayName prop properly", () => { - const component = shallow(); + const component = shallow(); expect(component.find(".panel-name-text").text()).toEqual("Mark"); - const component2 = shallow(); + const component2 = shallow(); expect(component2.find(".panel-name-text").text()).toEqual("Joe Bruin"); }); it("handles photoName prop properly", () => { - const component = shallow(); + const component = shallow(); expect(component.find(".panel-image").get(0).props.src).toBe(PHOTO_NAMES[DEFAULT_PHOTO_NAME]); const name = Object.keys(PHOTO_NAMES)[1]; - const component2 = shallow(); + const component2 = shallow(); expect(component2.find(".panel-image").get(0).props.src).toBe(PHOTO_NAMES[name]); }); it("Buttons change based on content type", () => { - const component = shallow(); + const component = shallow(); expect(component.find("#editor-button")); expect(component.find("#sign-out-button")); - const component2 = shallow(); + const component2 = shallow(); expect(component2.find("#sketches-button")); expect(component2.find("#sign-out-button")); }); @@ -55,19 +55,19 @@ describe("ProfilePanel", () => { }); it("handles displayName prop properly", () => { - const component = shallow(); + const component = shallow(); expect(component.find(".panel-image").get(0).props.src).toBe(PHOTO_NAMES[DEFAULT_PHOTO_NAME]); const name = Object.keys(PHOTO_NAMES)[1]; - const component2 = shallow(); + const component2 = shallow(); expect(component2.find(".panel-image").get(0).props.src).toBe(PHOTO_NAMES[name]); }); it("changing the panel image works", () => { const clickFn = jest.fn(photo => photo); - const component = shallow(); + const component = shallow(); expect(component.find(".panel-image").get(0).props.src).toBe(PHOTO_NAMES[DEFAULT_PHOTO_NAME]); @@ -104,7 +104,9 @@ describe("ProfilePanel", () => { it("changing the display name works", () => { const clickFn = jest.fn(name => name); - const component = shallow(); + const component = shallow( + , + ); expect(component.find(".panel-name-text").text()).toBe("Mark"); @@ -146,6 +148,8 @@ describe("ProfilePanel", () => { // TODO: + // test the non-logged in profile panel! + //test that onBlur causes a submit for the display name input //test the options list buttons onclick functionality diff --git a/src/components/containers/MainContainer.js b/src/components/containers/MainContainer.js index 1ad79f51..49c49da1 100644 --- a/src/components/containers/MainContainer.js +++ b/src/components/containers/MainContainer.js @@ -47,9 +47,6 @@ const mapDispatchToProps = dispatch => { }; }; -const MainContainer = connect( - mapStateToProps, - mapDispatchToProps, -)(Main); +const MainContainer = connect(mapStateToProps, mapDispatchToProps)(Main); export default MainContainer; diff --git a/src/components/containers/ViewOnlyContainer.js b/src/components/containers/ViewOnlyContainer.js new file mode 100644 index 00000000..97b2bbd4 --- /dev/null +++ b/src/components/containers/ViewOnlyContainer.js @@ -0,0 +1,35 @@ +import ViewOnly from "../ViewOnly.js"; +import { connect } from "react-redux"; +import { setOutput } from "../../actions/outputActions.js"; +import { setProgramCode } from "../../actions/programsActions.js"; +import { togglePanel } from "../../actions/uiActions.js"; +import { setTheme } from "../../actions/uiActions.js"; +import { CLOSED_PANEL_LEFT, OPEN_PANEL_LEFT, PANEL_SIZE } from "../../constants"; + +const mapStateToProps = state => { + const { mostRecentProgram } = state.userData; + return { + uid: state.userData.uid, + screenWidth: state.ui.screenWidth, + screenHeight: state.ui.screenHeight, + panelOpen: state.ui.panelOpen, + left: (state.ui.panelOpen ? OPEN_PANEL_LEFT : CLOSED_PANEL_LEFT) + PANEL_SIZE, + theme: state.ui.theme, + mostRecentProgram, + }; +}; + +const mapDispatchToProps = dispatch => { + return { + runCode: (code, language) => dispatch(setOutput(code, language)), + togglePanel: () => dispatch(togglePanel()), + setProgramCode: (program, code) => { + dispatch(setProgramCode(program, code)); + }, + setTheme: theme => dispatch(setTheme(theme)), + }; +}; + +const ViewOnlyContainer = connect(mapStateToProps, mapDispatchToProps)(ViewOnly); + +export default ViewOnlyContainer; diff --git a/src/lib/fetch.js b/src/lib/fetch.js index f3280da6..3d9b3e62 100644 --- a/src/lib/fetch.js +++ b/src/lib/fetch.js @@ -41,26 +41,27 @@ export const getUserData = async (uid = "", includePrograms = false) => { */ const makeServerRequest = (data, endpoint, method = "post") => { - let body = ""; - - // if the passed-in data object has at least 1 key, set the body to the stringified data object - try { - if (Object.keys(data).length) { - body = JSON.stringify(data); - } - } catch (err) { - console.log(err); - return; - } - - const options = { + let options = { method: method, headers: { "Content-Type": "application/json; charset=utf-8", }, - body, }; + if (method === "post" || method === "put") { + let body = ""; + // if the passed-in data object has at least 1 key, set the body to the stringified data object + try { + if (Object.keys(data).length) { + body = JSON.stringify(data); + } + } catch (err) { + console.log(err); + return; + } + options.body = body; + } + return fetch(`${constants.SERVER_URL}/${endpoint}`, options); }; @@ -103,3 +104,15 @@ export const createSketch = data => { export const deleteSketch = data => { return makeServerRequest(data, "deleteProgram"); }; + +/** + * gets a sketch's information by the docID + * @param {string} docID the key for the requested program in the top-level programs object + */ + +export const getSketch = async docID => { + const endpoint = `getProgram/${docID}`; + let result = await makeServerRequest({}, endpoint, "get"); + let { ok, sketch } = await result.json(); + return { ok, sketch }; +}; diff --git a/src/styles/Editor.scss b/src/styles/Editor.scss index 4482ec35..462674d2 100644 --- a/src/styles/Editor.scss +++ b/src/styles/Editor.scss @@ -37,6 +37,17 @@ } +.program-sketch-name { + flex: 0 1 auto; + @include themify($themes) { + color: themed("color"); + } + max-width: 200px; + font-size: 24px; + margin-left: 1em; +} + + .code-section-banner { display: flex; flex-direction: row; diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 7097f698..bf2df0a9 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -46,7 +46,8 @@ $themes: ( outputBackground: #e6e6fa, accentColor: $white, accentBackground: $light-lavender, - /* Card variables */ cardColor: $theme-dark, + /* Card variables */ + cardColor: $theme-dark, cardBackground: $white, cardBorder: 1px solid $light-lavender, cardDivider: $light-lavender, @@ -68,7 +69,8 @@ $themes: ( outputBackground: $theme-tertiary, accentColor: white, accentBackground: $theme-light, - /* Card variables */ cardColor: $theme-dark, + /* Card variables */ + cardColor: $theme-dark, cardBackground: $off-white, cardBorder: none, cardDivider: $theme-dark,