diff --git a/@empirica-mocks/core/mocks.js b/@empirica-mocks/core/mocks.js index 4f338f2..da61a3b 100644 --- a/@empirica-mocks/core/mocks.js +++ b/@empirica-mocks/core/mocks.js @@ -1,3 +1,12 @@ +import { useContext } from 'react'; +import { isFunctionDeclaration } from "typescript"; +// import { StageContext } from '@/editor/stageContext'; # don't know why this doesn't work + +// file is in deliberation-empirica/client/node_modules/@empirica/core/mocks.js +import { StageContext } from "../../../../../src/app/editor/stageContext" + + + export function usePlayer() { // This is a mock function that returns a mock player object // console.log("loaded usePlayer() from react-mocks.js"); @@ -5,6 +14,7 @@ export function usePlayer() { isMock: true, introDone: true, exitStep: 0, //TODO, + gameID: 21, position: 0, //TODO - set with toggle get: function (varName) { return this[varName]; @@ -34,10 +44,13 @@ export function useGame() { } export function useStageTimer() { - //TODO implement? + const stage = useContext(StageContext); + console.log("useStageTimerMock", stage) + // This is a mock function that returns a mock stage timer object const stageTimer = { isMock: true, + elapsed: stage.elapsed // problem: this will be called every render cycle... }; return stageTimer; @@ -45,15 +58,36 @@ export function useStageTimer() { export function useStage() { // This is a mock function that returns a mock stage object + + const { + currentStageIndex, + setCurrentStageIndex, + elapsed, + setElapsed, + treatment, + setTreatment, + } = useContext(StageContext) + // const stage1 = useContext(StageContext); + // console.log("useStageMock", stage1) + const stage = { isMock: true, - index: 0, //TODO get: function (varName) { - return this[varName]; - }, - set: function (varName, value) { - this[varName] = value; + //const currentStageIndex = int(localStorage.getItem("currentStageIndex")); + + //const treatmentString = localStorage.getItem("treatment"); + //const treatment = JSON.parse(treatmentString); + if (varName === "elements") { + return treatment.gameStages[currentStageIndex]?.elements + } else if (varName === "discussion") { + return treatment.gameStages[currentStageIndex]?.discussion + } else if (varName === "name") { + return treatment.gameStages[currentStageIndex]?.name + } else if (varName === "index") { + return currentStageIndex + } }, + }; return stage; @@ -73,3 +107,30 @@ export function usePlayers() { return players; } + +export function useGlobal() { + // This is a mock function that returns a mock global object + const global = { + isMock: true, + recruitingBatchConfig: { + cdn: 'prod', + }, + resourceLookup: { + cdn: 'prod', + }, + cdnList: { + test: "http://localhost:9091", + local: "http://localhost:9090", + prod: "https://s3.amazonaws.com/assets.deliberation-lab.org", + }, + get: function (varName) { + return this[varName]; + } + }; + return global; +} + +// Mock implementation of Loading +export function Loading() { + return "Loading..."; +} diff --git a/@empirica-mocks/core/package.json b/@empirica-mocks/core/package.json index e7a17a4..5350664 100644 --- a/@empirica-mocks/core/package.json +++ b/@empirica-mocks/core/package.json @@ -39,7 +39,7 @@ "require": "./dist/player.cjs" }, "./player/react": { - "import": "./react-mocks.js" + "import": "./mocks.js" }, "./player/classic": { "types": "./dist/player-classic.d.ts", diff --git a/cypress/e2e/test.cy.ts b/cypress/e2e/test.cy.ts index d1acde8..bc5eaae 100644 --- a/cypress/e2e/test.cy.ts +++ b/cypress/e2e/test.cy.ts @@ -26,7 +26,7 @@ describe('test spec', () => { cy.get('[data-cy="add-element-button-0"]').click() cy.get('[data-cy="edit-element-name-0-new"]').type("Element 1") cy.get('[data-cy="edit-element-type-0-new"]').select("Prompt") - cy.get('[data-cy="edit-element-file-0-new"]').type("file/address") + cy.get('[data-cy="edit-element-file-0-new"]').type("projects/example/preDiscussionInstructions.md") cy.get('[data-cy="edit-element-save-0-new"]').click() cy.get('[data-cy="element-0-0"]').contains("prompt").should("be.visible") @@ -43,6 +43,12 @@ describe('test spec', () => { cy.get('[data-cy="element-0-1"]').contains("survey").should("be.visible") cy.get('[data-cy="element-0-1"]').contains("TIPI").should("be.visible") + // view first stage in render panel + cy.get('[data-cy="render-panel"]').contains("Click on a stage card to preview the stage from a participant view.").should("be.visible") + cy.get('[data-cy="stage-0"]').click(0, 0) + cy.get('[data-cy="render-panel"]').contains("Click on a stage card to preview the stage from a participant view.").should("not.exist") + cy.get('[data-cy="render-panel"]').contains("Here are a number of personality traits").should("be.visible") + // create second stage cy.get('[data-cy="add-stage-button"]').click() cy.get('[data-cy="edit-stage-name-new"]').type("Stage 2") @@ -62,10 +68,15 @@ describe('test spec', () => { cy.get('[data-cy="element-1-0"]').contains("video").should("be.visible") cy.get('[data-cy="element-1-0"]').contains("https://www.youtube.com/").should("be.visible") + // view second stage in render panel + cy.get('[data-cy="stage-1"]').click(0, 0) + cy.get('[data-cy="render-panel"]').contains("Click on a stage card to preview the stage from a participant view.").should("not.exist") + cy.get('[data-cy="render-panel"]').contains("Click to continue the video").should("be.visible") + // edit first element cy.get('[data-cy="edit-element-button-0-0"]').click() cy.get('[data-cy="edit-element-name-0-0"]').should("have.value", "Element 1").should("be.visible").type(" Edited") - cy.get('[data-cy="edit-element-file-0-0"]').type("edited/file/address") + cy.get('[data-cy="edit-element-file-0-0"]').type("projects/example/discussionInstructions.md") cy.get('[data-cy="edit-element-save-0-0"]').click() cy.get('[data-cy="element-0-0"]').contains("prompt").should("be.visible") diff --git a/deliberation-empirica b/deliberation-empirica index 467ff92..9adce4f 160000 --- a/deliberation-empirica +++ b/deliberation-empirica @@ -1 +1 @@ -Subproject commit 467ff9237d52c5136df79991b126e1e5c422bdc0 +Subproject commit 9adce4ff2683409eb27a6f63c68204a671278530 diff --git a/src/app/editor/components/CodeEditor.tsx b/src/app/editor/components/CodeEditor.tsx index 2cf00e8..0ce6834 100644 --- a/src/app/editor/components/CodeEditor.tsx +++ b/src/app/editor/components/CodeEditor.tsx @@ -44,7 +44,6 @@ export default function CodeEditor() { duration: 600, desc: 'Main Discussion Time', discussion: { - chatType: 'text', showNickname: false, showTitle: true, }, @@ -117,6 +116,7 @@ export default function CodeEditor() { localStorage.setItem('code', code) window.location.reload() //refresh page to make elements appear on screen } catch (YAMLParseError) { + console.log('Parse Error on Save', YAMLParseError) //TODO also display a little something went wrong pop up } } diff --git a/src/app/editor/components/EditElement.tsx b/src/app/editor/components/EditElement.tsx index a768b3b..a19e730 100644 --- a/src/app/editor/components/EditElement.tsx +++ b/src/app/editor/components/EditElement.tsx @@ -5,7 +5,7 @@ import { ElementType, elementSchema, elementBaseSchema, -} from '@/../deliberation-empirica/server/src/preFlight/validateTreatmentFile' +} from '../../../../deliberation-empirica/server/src/preFlight/validateTreatmentFile' import { zodResolver } from '@hookform/resolvers/zod' import * as zod from 'zod' diff --git a/src/app/editor/components/EditStage.tsx b/src/app/editor/components/EditStage.tsx index 974dbcb..f6349e7 100644 --- a/src/app/editor/components/EditStage.tsx +++ b/src/app/editor/components/EditStage.tsx @@ -6,7 +6,7 @@ import { stageSchema, StageType, ElementType, -} from '@/../deliberation-empirica/server/src/preFlight/validateTreatmentFile' +} from '../../../../deliberation-empirica/server/src/preFlight/validateTreatmentFile' import { zodResolver } from '@hookform/resolvers/zod' import * as zod from 'zod' diff --git a/src/app/editor/components/ElementCard.tsx b/src/app/editor/components/ElementCard.tsx index 6ed30fe..baf4291 100644 --- a/src/app/editor/components/ElementCard.tsx +++ b/src/app/editor/components/ElementCard.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react' import { Modal } from './Modal' import { EditElement } from './EditElement' -import { TreatmentType } from '@/../deliberation-empirica/server/src/preFlight/validateTreatmentFile' +import { TreatmentType } from '../../../../deliberation-empirica/server/src/preFlight/validateTreatmentFile' export function ElementCard({ element, diff --git a/src/app/editor/components/RenderPanel.tsx b/src/app/editor/components/RenderPanel.tsx index b78af59..7426276 100644 --- a/src/app/editor/components/RenderPanel.tsx +++ b/src/app/editor/components/RenderPanel.tsx @@ -1,32 +1,64 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState, useContext } from 'react' +import dynamic from 'next/dynamic.js' import TimePicker from './TimePicker' //import { Stage } from './../../../.././deliberation-empirica/client/src/Stage.jsx' import RenderDelibElement from './RenderDelibElement' -export function RenderPanel({ renderPanelStage }: { renderPanelStage: any }) { + +import { StageContext } from '@/editor/stageContext' + +const Stage = dynamic( + () => + import('./../../../.././deliberation-empirica/client/src/Stage.jsx').then( + (mod) => mod.Stage + ) as any, + { + ssr: false, + } +) + +export function RenderPanel() { const [time, setTime] = useState(0) - const elements = renderPanelStage.elements - const stageName = renderPanelStage.title - const stageDuration = renderPanelStage.duration + + const { + currentStageIndex, + setCurrentStageIndex, + elapsed, + setElapsed, + treatment, + setTreatment, + } = useContext(StageContext) + console.log('RenderPanel.tsx current stage index', currentStageIndex) + console.log('Current Treatment', treatment) + + // const currentStageIndex = Number(localStorage.getItem('currentStageIndex')) + + //const treatment = + //const currentStage = treatment?.gameStages.?[currentStageIndex] + + //console.log('Current stage', localStorage.getItem('currentStageIndex')) return ( -
- {!stageName && ( +
+ {currentStageIndex === 'default' && (

Click on a stage card to preview the stage from a participant view.

)} - {stageName && ( -
-

Preview of {stageName}

+ {currentStageIndex !== 'default' && ( +
+

Preview of stage {currentStageIndex}

+ {/* need to retrieve stage duration from treatment */}
)} - {stageName &&
} -
+ {currentStageIndex !== 'default' && ( +
+ )} + {/*
{elements !== undefined && elements.map( (element: any, index: any) => @@ -38,15 +70,11 @@ export function RenderPanel({ renderPanelStage }: { renderPanelStage: any }) {
) )} -
+
*/} - {/* Currently causes build to fail */} - {/* -
- {stageName && }{' '} - Replace custom rendering logic with Stage component +
+ {currentStageIndex !== 'default' && }
- */}
) } diff --git a/src/app/editor/components/StageCard.tsx b/src/app/editor/components/StageCard.tsx index 8c80fd9..d3ceaff 100644 --- a/src/app/editor/components/StageCard.tsx +++ b/src/app/editor/components/StageCard.tsx @@ -1,7 +1,7 @@ 'use client' -import React, { useState } from 'react' +import React, { useState, useContext } from 'react' import { ElementCard } from './ElementCard' -import { cn } from '@/app/components/utils' +import { cn } from '@/components/utils' import { Modal } from './Modal' import { EditStage } from './EditStage' import { EditElement } from './EditElement' @@ -9,6 +9,10 @@ import { TreatmentType, DurationType, } from '../../../../deliberation-empirica/server/src/preFlight/validateTreatmentFile' +import { setCurrentStageIndex } from './utils' +import { useStage } from '../../../../@empirica-mocks/core/mocks' + +import { StageContext } from '../stageContext.jsx' export function StageCard({ title, @@ -31,6 +35,9 @@ export function StageCard({ stageIndex: number setRenderPanelStage: any }) { + const { currentStageIndex, setCurrentStageIndex, elapsed, setElapsed } = + useContext(StageContext) + const addElementOptions = [ { question: 'Name', responseType: 'text' }, { @@ -57,12 +64,19 @@ export function StageCard({ const width = duration ? scale * duration : 'auto' function handleStageClick() { - setRenderPanelStage({ - title: title, - elements: elements, - duration: duration, - stageIndex: stageIndex, - }) + // setCurrentStageIndex(stageIndex) + // console.log('setting current stage to ', stageIndex) + //@ts-ignore + + console.log('setting current stage to ', stageIndex) + setCurrentStageIndex(stageIndex) + + // setRenderPanelStage({ + // title: title, + // elements: elements, + // duration: duration, + // stageIndex: stageIndex, + // }) } const newElementModalId = `modal-stage${stageIndex}-element-new` diff --git a/src/app/editor/components/Timeline.tsx b/src/app/editor/components/Timeline.tsx index a056a9f..86cb41a 100644 --- a/src/app/editor/components/Timeline.tsx +++ b/src/app/editor/components/Timeline.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState, useContext } from 'react' import { parse } from 'yaml' import { StageCard } from './StageCard' import TimelineTools from './TimelineTools' @@ -8,6 +8,7 @@ import { stringify } from 'yaml' import { Modal } from './Modal' import { EditStage } from './EditStage' import { TreatmentType } from '../../../../deliberation-empirica/server/src/preFlight/validateTreatmentFile' +import { StageContext } from '@/editor/stageContext' export default function Timeline({ setRenderPanelStage, @@ -15,7 +16,15 @@ export default function Timeline({ setRenderPanelStage: any }) { const [scale, setScale] = useState(1) // pixels per second - const [treatment, setTreatment] = useState(null) + //const [treatment, setTreatment] = useState(null) + const { + currentStageIndex, + setCurrentStageIndex, + elapsed, + setElapsed, + treatment, + setTreatment, + } = useContext(StageContext) function editTreatment(newTreatment: TreatmentType) { setTreatment(newTreatment) @@ -31,7 +40,7 @@ export default function Timeline({ const parsedCode = parse(codeStr) setTreatment(parsedCode) } - }, []) + }, [setTreatment]) if (!treatment) { return null diff --git a/src/app/editor/components/utils.ts b/src/app/editor/components/utils.ts new file mode 100644 index 0000000..55a8b80 --- /dev/null +++ b/src/app/editor/components/utils.ts @@ -0,0 +1,20 @@ +export function setCurrentStageIndex(index: number) { + localStorage.setItem('currentStageIndex', index.toString()) +} + +// export function setTreatment(setTreatment: ) { +// setTreatment(newTreatment) +// localStorage.setItem('code', stringify(newTreatment)) +// window.location.reload() +// } +// // Todo: think about using 'useContext' here instead of passing editTreatment all the way down + +// useEffect(() => { +// // Access localStorage only on the client side +// if (typeof window !== 'undefined') { +// const codeStr = localStorage.getItem('code') || '' +// const parsedCode = parse(codeStr) +// setTreatment(parsedCode) +// } +// }, []) + diff --git a/src/app/editor/page.tsx b/src/app/editor/page.tsx index 8162ea8..0399713 100644 --- a/src/app/editor/page.tsx +++ b/src/app/editor/page.tsx @@ -1,55 +1,66 @@ -"use client"; -import React, {useState} from "react"; -import { useResizable } from "react-resizable-layout"; -import DraggableSplitter from "../components/DraggableSplitter"; -import CodeEditor from "./components/CodeEditor"; -import { RenderPanel } from "./components/RenderPanel"; -import Timeline from "./components/Timeline"; +'use client' +import React, { useState } from 'react' +import { useResizable } from 'react-resizable-layout' +import DraggableSplitter from '../components/DraggableSplitter' +import CodeEditor from './components/CodeEditor' +import { RenderPanel } from './components/RenderPanel' +import Timeline from './components/Timeline' + +import { StageProvider } from './stageContext.jsx' + +const defaultStageContext = { + currentStageIndex: undefined, + elapsed: 0, +} export default function EditorPage({}) { const { position: leftWidth, separatorProps: codeSeparatorProps } = useResizable({ - axis: "x", + axis: 'x', initial: 1000, min: 100, - }); + }) + + console.log('page.tsx, context', defaultStageContext) const { position: upperLeftHeight, separatorProps: timelineSeparatorProps } = useResizable({ - axis: "y", - initial: 500 - }); - + axis: 'y', + initial: 500, + }) + const [renderElements, setRenderElements] = useState([]) const [renderPanelStage, setRenderPanelStage] = useState({}) return ( -
-
+ +
- -
+
+ +
- + -
- +
+ +
-
- + -
- +
+ +
-
- ); + + ) } diff --git a/src/app/editor/stageContext.jsx b/src/app/editor/stageContext.jsx new file mode 100644 index 0000000..80a3b82 --- /dev/null +++ b/src/app/editor/stageContext.jsx @@ -0,0 +1,70 @@ +//import { set } from 'node_modules/cypress/types/lodash'; +import { createContext, useState } from 'react'; + +// export const StageContext = createContext({ +// currentStageIndex: "default", +// elapsed: "default" +// }); + +const StageContext = createContext(); + +const StageProvider = ({ children }) => { + const [currentStageIndex, setCurrentStageIndex] = useState('default'); + const [elapsed, setElapsed] = useState('default'); + const [treatment, setTreatment] = useState(null); + + const contextValue = { + currentStageIndex, + setCurrentStageIndex, + elapsed, + setElapsed, + treatment, + setTreatment + }; + + return ( + + {children} + + ); +}; + +export { StageContext, StageProvider }; + +// import React, { createContext, useState, useContext } from 'react'; + +// // Create the context +// const StageContext = createContext(); + +// // Create a custom hook for easy access to the context +// const useStageContext = () => useContext(StageContext); + +// // Create the provider component +// const StageProvider = ({ children }) => { +// const [state, setState] = useState({ +// currentStageIndex: 'default', +// elapsed: 'default' +// }); + +// const setCurrentStageIndex = (value) => { +// setState((prevState) => ({ +// ...prevState, +// currentStageIndex: value +// })); +// }; + +// const setElapsed = (value) => { +// setState((prevState) => ({ +// ...prevState, +// elapsed: value +// })); +// }; + +// return ( +// +// {children} +// +// ); +// }; + +// export { StageProvider, useStageContext }; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7a97ca9..cf4c7a2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,20 +1,20 @@ -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; -import "./globals.css"; -import { SessionProvider } from "./AuthProvider"; -import Navbar from "./components/Navbar"; +import type { Metadata } from 'next' +import { Inter } from 'next/font/google' +import './globals.css' +import { SessionProvider } from './AuthProvider' +import Navbar from './components/Navbar' -const inter = Inter({ subsets: ["latin"] }); +const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; + title: 'CSSLab Researcher Portal', + description: 'CSSLab Researcher Portal', +} export default function RootLayout({ children, }: { - children: React.ReactNode; + children: React.ReactNode }) { return ( @@ -26,5 +26,5 @@ export default function RootLayout({ - ); + ) } diff --git a/tsconfig.json b/tsconfig.json index 9e6c212..d84b93f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,10 +18,11 @@ "name": "next" } ], + "baseUrl": ".", "paths": { - "@/*": ["./src/*"], - "@/api/*": ["./src/app/api/*"], - "@deliberation-empirica": ["./deliberation-empirica"] + "@/*": ["src/app/*"], + "@/api/*": ["src/app/api/*"], + "@deliberation-empirica": ["deliberation-empirica"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],