diff --git a/crowdsourcing/constraint_events/webapp/package-lock.json b/crowdsourcing/constraint_events/webapp/package-lock.json index 9594b9401..691ce9593 100644 --- a/crowdsourcing/constraint_events/webapp/package-lock.json +++ b/crowdsourcing/constraint_events/webapp/package-lock.json @@ -16,7 +16,8 @@ "react-bootstrap": "^1.5.0", "react-dom": "16.13.1", "react-dropdown": "^1.9.2", - "react-icons": "^4.2.0" + "react-icons": "^4.2.0", + "react-tippy": "^1.4.0" }, "devDependencies": { "@babel/cli": "^7.1.0", @@ -5591,6 +5592,14 @@ "regenerator-runtime": "^0.13.4" } }, + "node_modules/react-tippy": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/react-tippy/-/react-tippy-1.4.0.tgz", + "integrity": "sha512-r/hM5XK9Ztr2ZY7IWKuRmISTlUPS/R6ddz6PO2EuxCgW+4JBcGZRPU06XcVPRDCOIiio8ryBQFrXMhFMhsuaHA==", + "dependencies": { + "popper.js": "^1.11.1" + } + }, "node_modules/react-transition-group": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", @@ -12059,6 +12068,14 @@ } } }, + "react-tippy": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/react-tippy/-/react-tippy-1.4.0.tgz", + "integrity": "sha512-r/hM5XK9Ztr2ZY7IWKuRmISTlUPS/R6ddz6PO2EuxCgW+4JBcGZRPU06XcVPRDCOIiio8ryBQFrXMhFMhsuaHA==", + "requires": { + "popper.js": "^1.11.1" + } + }, "react-transition-group": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", diff --git a/crowdsourcing/constraint_events/webapp/package.json b/crowdsourcing/constraint_events/webapp/package.json index 80c022aa0..6ebad191e 100644 --- a/crowdsourcing/constraint_events/webapp/package.json +++ b/crowdsourcing/constraint_events/webapp/package.json @@ -18,7 +18,8 @@ "react-bootstrap": "^1.5.0", "react-dom": "16.13.1", "react-dropdown": "^1.9.2", - "react-icons": "^4.2.0" + "react-icons": "^4.2.0", + "react-tippy": "^1.4.0" }, "devDependencies": { "@babel/cli": "^7.1.0", diff --git a/crowdsourcing/constraint_events/webapp/src/TaskCopy.js b/crowdsourcing/constraint_events/webapp/src/TaskCopy.js index 1eabcaeb2..33887e8bc 100644 --- a/crowdsourcing/constraint_events/webapp/src/TaskCopy.js +++ b/crowdsourcing/constraint_events/webapp/src/TaskCopy.js @@ -1,12 +1,203 @@ +//HEADER SCREENSHOT +const HeaderShot = require("./assets/images/Tutorial/HeaderShot.png"); +//EVENT SCREENSHOTS +const EventShot1 = require("./assets/images/Tutorial/Event/EventShot1.png"); +const EventShot2 = require("./assets/images/Tutorial/Event/EventShot2.png"); +const EventShot3 = require("./assets/images/Tutorial/Event/EventShot3.png"); +const EventShot4 = require("./assets/images/Tutorial/Event/EventShot4.png"); +const EventShot5 = require("./assets/images/Tutorial/Event/EventShot5.png"); + + +//CONSTRAINT SCREENSHOTS +const ConstraintShot1 = require("./assets/images/Tutorial/Constraint/ConstraintShot1.png"); +const ConstraintShot2 = require("./assets/images/Tutorial/Constraint/ConstraintShot2.png"); +const ConstraintShot3 = require("./assets/images/Tutorial/Constraint/ConstraintShot3.png"); +//const ConstraintShot4 = require("./assets/images/Tutorial/Constraint/ConstraintShot4.png"); +const ConstraintShot5 = require("./assets/images/Tutorial/Constraint/ConstraintShot5.png"); + + const Copy ={ + tutorialIntro:{ + explanation:"We're trying to crowdsource interactions between two objects. These interactions will be set in a medieval fantasy scenario, and as such should not refer to real people, places, or modern day technologies. The objective of this task is to focus on two aspects of the interaction: The events that occur during the interaction and the constraints required for the interaction to take place. At the top of the screen you will be presented with 3 cards, the first two will be the names and a brief description of the objects involved in the interaction, the third contains the narration for the interaction itself. Reference this information while completing the task.", + screenshot: HeaderShot + }, constraint:{ - questions:{ + tutorialIntro:"The Constraints section focuses on what conditions must be met in order for this interaction to occur. So for this portion you will be thinking in terms of what must be true for the interaction to have happened in the first place based on the narration.", + questions:[ + " Constraints for Interaction: ", + "1. Does # need to be held?", + "2. Could one use # with # and expect the same outcome?", + "3. Can this operation be done an infinite number of times?", + "3a. How many more times can it be done?", + "4. Would this have to happen in a specific place?", + "4a. Where would that location be?", + ], + tutorialCopy:[ + { + question:"1) Attributes", + explanation:`If the interaction requires that the object have + certain attributes or conditions, they should be listed. - } + For instance, if the interaction is "You use the torch + to ignite the pile of logs on the floor. It catches + quickly and begins to burn." then you can add the + constraint that the logs must not be wet. + + Use common sense understanding to determine + constraints that are relevant for your example.`, + screenshot: ConstraintShot1 + }, + { + question:"2) Does object2 need to be held?", + explanation:`We generally assume that the actor is already holding the first item in the interaction. If the interaction requires the actor to be holding both objects (like if the actor is combining objects), you should mark it here. For example, for the interaction "You put the gem into the key, then turn it over in your hands. It it's a perfect fit." you would say both objects need to be held. In contrast for the interaction "You swing the axe at the tree and it rebounds back. Not a mark, this tree must be magic." You would answer that the tree does not need to be held.`, + screenshot: ConstraintShot2 + }, + { + question:"3) Can this operation be done an infinite number of times?", + explanation:`Some interactions are limited in the number of uses. Here + you should estimate the number of times the interaction + could be repeated. For instance, for the interaction: "You + give a piece of the pie to the fox. It eats it quickly and scurries + back away from you." This kind of interaction must have some + kind of limit, as your pie has a constant amount. As such you + may say this interaction could be done 12 times. While we + don't expect an exact number on this, an estimation will be fine. + + In contrast "You swing the stick at the bucket and it rings out + loudly, likely annoying anyone in earshot" is an interaction + that could probably be done indefinitely.` + }, + { + question:"4) Could one use Y with X and expect the same outcome?", + explanation:`Some interactions could be considered to have a direction, for + instance "using an axe with a tree" is different from "using a + tree with an axe", though the second here doesn't even make + sense. However, an interaction like "you mix the milk and the + flour to start creating some dough" could work with either + "use milk with flour" or "use flour with milk". + + For unidirectional cases like the former, this would be false. + For bidirectional cases like the latter, it would be true.`, + screenshot: ConstraintShot3 + }, + { + question:"5) Does this interaction need to have in a specific place?", + explanation:`If the interaction description implies that the interaction + occurs somewhere specifically, provide a name for the place + you believe it is happening. + + If it doesn't have any specification, then leave blank. + + For instance the interaction "You mix the the flour and milk + together to start creating some dough." could happen anywhere, + so you should say no for an interaction like this. + + However, for something like "You throw the spear at the dragon + and it recoils, spraying fire breath into the forest around you" then + this interaction must occur in the forest as it is implied by the + narration.`, + screenshot: ConstraintShot5 + } + ] }, event:{ + tutorialIntro:"The events section pertains to what changes(if any) occured during the interaction. You will be tasked with narrating what this interaction looks like to a third party then describing anything that was created, destroyed, or changed as result of the interaction.", questions:{ + 1: "1. Narrate this interaction to another observer who sees it happen.", + 2: "2. Are objects removed?", + a2: "2a. Which object(s)?", + 3: "3. Does an object's description change?", + 4: "4. Are objects created?", + setter: " After this action: " + }, + tutorialCopy:[ + { + question:"1) Narrate this interaction", + explanation:'The new narration should be directed to someone observing the interaction take place, say in the same location. If you want to refer to the actor, location, or either the key or the lock, use `ACTOR`, `LOCATION`, `OBJECT1`, `OBJECT2`. For example, "You place the key in the lock and turn. After a satisfying click the lock becomes unlocked " could be seen as "ACTOR fumbles with a OBJECT1 in the OBJECT2 for a moment, before you hear a click echo through LOCATION."', + screenshot: EventShot1 + }, + { + question:"2) Are objects removed?", + explanation:`If the interaction would cause one of the + used objects not exist anymore, mark those + objects. + + For instance, if the interaction was "The lit + torch ignites the table, and the table burns + to the ground, leaving a pile of ashes." + In this case you would mark that an object + is removed, specifically the table. The torch + is not removed.`, + screenshot: EventShot2 + }, + { + question:"3) Do object descriptions change?", + explanation:`If an object remains in the scene, but it ends + up changed by the interaction, it's description + should change. For instance given "You + scratch the side of the shiny bucket with + a rusty key.", the description of the bucket + may change from "A shiny bucket that must + have been bought recently" to "A shiny new + bucket. It would be perfect, if not for the + deep scratches in one side."`, + screenshot: EventShot3 + }, + { + question:"4) Are objects created?", + explanation:`If the interaction creates new objects in the + scene, you should list them here. You should + describe the object and note where the object + exists. The object can only be created either the + location the interaction occurs (on the floor), in/on + one of the other objects (like on a table or in + a mug), or carried by the actor. + + So, for the example with the torch and + a wooden table, where the table burns to ashes, + you may create "Pile of Ash: These ashes are + fresh and still a little hot." and it would be created + in the room.`, + screenshot: EventShot4 + }, + { + question:"5) Attribute changes", + explanation:`If the interaction changes something physical + about an object, you should mark the attribute + changes here. For instance, if an interaction is + "You dip the towel in the bucket of oil. It seeps + into the fabric completely", you may add the + attributes "wet" and "flammable". This is because, + after the interaction, the towel must be wet and + flammable. + + You can also mark that, after the interaction, some + attributes must not be true. For instance, after the + above interaction, you could mark that the towel + will not be "clean" afterwards.`, + screenshot: EventShot5 + } + ] + }, + errorKey:{ + events:{ + q1Blank: "Narration cannot be blank", + q2Null: "Please click yes or no for object removal event", + q2Empty:"Please select the item that was removed or click no for this event.", + q3Null: "Please click yes or no for object description change event", + q3Blank:"Description cannot be blank", + q4Null: "Please click yes or no for object creation event", + q4MissingField: "Please click yes or no for object creation event" + }, + constraint:{ + q1Null: "Please click yes or no for held constraint", + q2Null: "Please click yes or no for reversible constraint", + q3Null: "Please click yes or no for times remaining constraint", + q3Blank: "Description cannot be blank", + q4Null: "Please click yes or no for location constraint", + q4blank: "Location cannot be blank please write a location or click no" } - } + } } +export default Copy diff --git a/crowdsourcing/constraint_events/webapp/src/app.jsx b/crowdsourcing/constraint_events/webapp/src/app.jsx index 89af79bb3..7b7d12d69 100644 --- a/crowdsourcing/constraint_events/webapp/src/app.jsx +++ b/crowdsourcing/constraint_events/webapp/src/app.jsx @@ -5,22 +5,29 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ - -import React from "react"; +//REACT +import React, {useEffect, useState} from "react"; import ReactDOM from "react-dom"; -import Task from "./views/Task"; -import Submission from "./components/Submission" import { TaskDescription } from "./components/task_description.jsx"; // import { ActionDescription } from "./components/action_description.jsx"; // import { ConstraintBlock } from "./components/constraint_element.jsx"; // import { EventsBlock } from "./components/event_elements.jsx"; -import LoadingScreen from "./components/LoadingScreen" +//MEPHISTO import { useMephistoTask } from "mephisto-task"; // import { TimesComponent } from "./components/times_component.jsx"; // import { SubmitButton } from "./components/submit_button.jsx"; - -import "./styles.css" -/* ================= Application Components ================= */ +//STYLING +import "./styles.css"; +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'react-tippy/dist/tippy.css' +//CUSTOM COMPONENTS +import Task from "./views/Task"; +import Preview from "./views/Preview"; +import Submission from "./components/Submission"; +import LoadingScreen from "./components/LoadingScreen"; +import ErrorToast from "./components/ErrorToast"; +//UTILS +import TaskCopy from "./TaskCopy"; function MainApp() { const { @@ -31,8 +38,35 @@ function MainApp() { initialTaskData, handleSubmit, } = useMephistoTask(); + //Error Handling + const [showError, setShowError] = useState(false); + const [errorMessages, setErrorMessages] = useState([]); + //Events State + const [broadcastMessage, setBroadcastMessage] = useState(""); + const [isCreatingEntity, setIsCreatingEntity] = useState(null); + const [createdEntity, setCreatedEntity] = useState({}); + const [isRemovingObjects, setIsRemovingObjects] = useState(null); + const [removedObjects, setRemovedObjects] = useState([]); + const [isChangingDescription, setIsChangingDescription] = useState(null); + //Primary + const [primaryModifiedAttributes, setPrimaryModifiedAttributes]= useState([]); + const [primaryDescription, setPrimaryDescription] = useState(""); + //Secondary + const [secondaryModifiedAttributes, setSecondaryModifiedAttributes]= useState([]); + const [secondaryDescription, setSecondaryDescription] = useState(""); + + //Constraint State + const [isSecondaryHeld, setIsSecondaryHeld] = useState(null); + const [isReversible, setIsReversible] = useState(null); + const [isInfinite, setIsInifinite] = useState(null) + const [timesRemaining, setTimesRemaining] = useState(0); + const [isLocationConstrained, setIsLocationConstrained] = useState(null); + const [constraintLocation, setConstraintLocation] = useState(""); + //Primary + const [primaryConstrainingAttributes, setPrimaryConstrainingAttributes]= useState([]); + //Secondary + const [secondaryConstrainingAttributes, setSecondaryConstrainingAttributes]= useState([]); - const [timesRemaining, setTimesRemaining] = React.useState(""); if (blockedReason !== null) { return ( @@ -47,11 +81,7 @@ function MainApp() { if (isPreview) { return (
-
-
- -
-
+
); } @@ -69,7 +99,7 @@ function MainApp() { const state = { 'constraints': [], 'events': [], - 'times_remaining': timesRemaining + 'times_remaining':"" } for (let i = 0; i < state['constraints'].length; i++) { @@ -97,15 +127,17 @@ function MainApp() { console.log('active? ', active); const dummyData= { object1: { - name: "key", - desc:"A ordinary key that will unlock or lock something.", - attributes:[ - {name: "hot", val: false}, - {name: "valuable", val: true}, - {name: "brittle", val: false} - ] + id:2, + name: "key", + desc:"A ordinary key that will unlock or lock something.", + attributes:[ + {name: "hot", val: false}, + {name: "valuable", val: true}, + {name: "brittle", val: false} + ] }, object2: { + id:2, name: "lock", desc:"A ordinary lock that will be unlocked or locked by a key.", attributes:[ @@ -114,34 +146,300 @@ function MainApp() { {name: "locked", val: true} ], }, - interaction: "You place the key in the lock and turn. After a satifying click the lock becomes unlocked." + interaction: "You place the key in the lock and turn. After a satisfying click the lock becomes unlocked." + } + + const submissionHandler = ()=>{ + let updatedEvents = [] + let updatedConstraints = [] + let updatedTimesRemaining + let updatedErrors =[] + console.log("BROADCAST MESSAGE", broadcastMessage, !broadcastMessage, broadcastMessage!==dummyData.interaction) +//ERROR HANDLING + //EVENT ERRORS + //BROADCAST MESSAGE + if(!broadcastMessage){ + updatedErrors.push(TaskCopy.errorKey.events.q1Blank) + } + if(broadcastMessage===dummyData.interaction){ + updatedErrors.push(TaskCopy.errorKey.events.q1Blank) + } + //REMOVE OBJECT + if(isRemovingObjects===null){ + updatedErrors.push(TaskCopy.errorKey.events.q2Null) + } + if(isRemovingObjects && !removedObjects.length){ + updatedErrors.push(TaskCopy.errorKey.events.q2Empty) + } + // DESCRIPTION + if(isChangingDescription===null){ + updatedErrors.push(TaskCopy.errorKey.events.q3Null) + } + if(isChangingDescription && (!primaryDescription || !secondaryDescription)){ + updatedErrors.push(TaskCopy.errorKey.events.q3Blank) + } + //CREATE ENTITY + if(isCreatingEntity===null){ + updatedErrors.push(TaskCopy.errorKey.events.q4Null) + } + //CONSTRAINT ERRORS + //HELD + if(isSecondaryHeld===null){ + updatedErrors.push(TaskCopy.errorKey.constraint.q1Null) + } + //REVERSIBLE + if(isReversible===null){ + updatedErrors.push(TaskCopy.errorKey.constraint.q2Null) + } + //TIMES REMAINING + if(isInfinite===null){ + updatedErrors.push(TaskCopy.errorKey.constraint.q3Null) + } + //LOCATIONS + if(isLocationConstrained===null){ + updatedErrors.push(TaskCopy.errorKey.constraint.q4Null) + } + // EVENT UPDATES + //BROADCASTMESSAGE + let updatedBroadcastMessage = broadcastMessage; + updatedBroadcastMessage = { + type: "broadcast_message", + params: { + self_view: dummyData.interaction, + room_view: broadcastMessage + } + } + updatedEvents = [...updatedEvents, updatedBroadcastMessage] + + //OBJECT REMOVAL EVENT + if(isRemovingObjects){ + let updatedRemovedObjects = removedObjects.map(obj=>( + {type:"remove_object", + params:{ + name:obj + } + })) + updatedEvents = [...updatedEvents, ...updatedRemovedObjects] + } + //ENTITY CREATION EVENT + if(isCreatingEntity){ + const {name, desc, location } = createdEntity; + let updatedCreatedEntityEvent = { + type: "create_entity", + params: { + "type": location, + object: { + name: name, + desc: desc + } + } + } + updatedEvents = [...updatedEvents, updatedCreatedEntityEvent] + } + //DESCRIPTION CHANGE EVENT + if(isChangingDescription){ + let updatedDescriptions = [ + { + type:"modify_attribute", + params:{ + type:"in_used_item", + key:"desc", + value: primaryDescription + } + }, + { + type:"modify_attribute", + params:{ + type:"in_used_target_item", + key:"desc", + value: secondaryDescription + } + } + ] + updatedEvents = [...updatedEvents, ...updatedDescriptions] + } + //ATTRIBUTE MODIFICATION EVENTS + if(primaryModifiedAttributes.length){ + let updatedPrimaryModifiedAttributes = primaryModifiedAttributes.map(attribute=>{ + console.log("PRIMARY ATTRIBUTES EVENT", attribute) + if(!attribute.name){ + return + } + return({ + type:"modify_attribute", + params:{ + type:"in_used_item", + key: attribute.name, + value: attribute.value + } + }) + }) + updatedEvents = [...updatedEvents, ...updatedPrimaryModifiedAttributes] + } + if(secondaryModifiedAttributes.length){ + let updatedSecondaryModifiedAttributes = secondaryModifiedAttributes.map(attribute=>{ + console.log("SECONDARY ATTRIBUTES EVENT", attribute) + if(!attribute.name){ + return + } + return ({ + type:"modify_attribute", + params:{ + type:"in_used_target_item", + key: attribute.name, + value: attribute.value + } + }) + }) + updatedEvents = [...updatedEvents, ...updatedSecondaryModifiedAttributes] + } + //CONSTRAINT UPDATES + //CONSTRAINING ATTRIBUTES + + if(primaryConstrainingAttributes.length){ + let updatedPrimaryConstrainingAttributes = primaryConstrainingAttributes.map(attribute=>{ + console.log("PRIMARY ATTRIBUTES CONSTRAINTS", attribute) + if(!attribute.name){ + return + } + return ({ + type:"attribute_compare_value", + params:{ + type:"in_used_item", + key: attribute.name, + list: [attribute.value], + cmp_type: "eq" + } + }) + }) + updatedConstraints = [...updatedConstraints, ...updatedPrimaryConstrainingAttributes] + } + if(secondaryConstrainingAttributes.length){ + let updatedSecondaryConstrainingAttributes = secondaryConstrainingAttributes.map(attribute=>{ + console.log("SECONDARY ATTRIBUTES CONSTRAINTS", attribute) + if(!attribute.name){ + return + } + return ({ + type:"attribute_compare_value", + params:{ + type:"in_used_target_item", + key: attribute.name, + list: [attribute.value], + cmp_type: "eq" + } + }) + }) + updatedConstraints = [...updatedConstraints, ...updatedSecondaryConstrainingAttributes] + } + // HELD CONSTRAINT + if(isSecondaryHeld){ + let updatedSecondaryHeldConstraint = { + type: "is_holding", + params: { + complement: "used_target_item" + } + } + updatedConstraints = [...updatedConstraints, updatedSecondaryHeldConstraint] + } + let updatedIsReversible = isReversible; + + //TIMES REMAINING CONSTRAINT + if(isInfinite){ + updatedTimesRemaining = "inf" + }else{ + updatedTimesRemaining = timesRemaining + } + + // LOCATION CONSTRAINT + if(isLocationConstrained){ + let updatedLocationConstraint = { + type: "in_room", + params: { + room_name: constraintLocation + } + } + updatedConstraints = [...updatedConstraints, updatedLocationConstraint] + } + // Actualy data payload properly formatted for submission + const payload = { + times_remaining: updatedTimesRemaining, + reversible: updatedIsReversible, + events: updatedEvents, + constraints: updatedConstraints + } + // If the function has reached this point with an empty error array the payload is ready. + if(!updatedErrors.length){ + console.log(payload) + //handleSubmit(payload) + }else{ + // Each error in the updatedErrors Array will be listed in the Error Toast + setErrorMessages(updatedErrors) + setShowError(true) + } } return (
- + setShowError(false)}/> +
- +
); } ReactDOM.render(, document.getElementById("app")); - - -/*
-
- -
-
- -
- -
- -
- -
- -
-
*/ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot1.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot1.png new file mode 100644 index 000000000..c4e81dd2f Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot1.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot2.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot2.png new file mode 100644 index 000000000..2b6a7dd38 Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot2.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot3.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot3.png new file mode 100644 index 000000000..b3191898f Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot3.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot4.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot4.png new file mode 100644 index 000000000..c5d51187d Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot4.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot5.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot5.png new file mode 100644 index 000000000..cb040b350 Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Constraint/ConstraintShot5.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot1.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot1.png new file mode 100644 index 000000000..210393797 Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot1.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot2.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot2.png new file mode 100644 index 000000000..8ac44a2e0 Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot2.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot3.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot3.png new file mode 100644 index 000000000..71669471f Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot3.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot4.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot4.png new file mode 100644 index 000000000..56345a564 Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot4.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot5.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot5.png new file mode 100644 index 000000000..333dfab8a Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/Event/EventShot5.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/HeaderShot.png b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/HeaderShot.png new file mode 100644 index 000000000..4f8cdf628 Binary files /dev/null and b/crowdsourcing/constraint_events/webapp/src/assets/images/Tutorial/HeaderShot.png differ diff --git a/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/AttributeRow.js b/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/AttributeRow.js index 4d658ab63..18b774b63 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/AttributeRow.js +++ b/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/AttributeRow.js @@ -2,21 +2,39 @@ import React, {useState, useEffect, useRef} from "react"; import TaskButton from "../TaskButton" import { MdCancel } from "react-icons/md"; - -const AttributeRow = ({attribute, objectName, objectColor, isConstraint, deleteHandler})=>{ +//AttributeRow - Tracks names and values for each attribute and allows used to edit the names and values with buttons assigning a boolean value and an input to change name of attribute +const AttributeRow = ({ + attribute,//Attribute object containing name and val keys + objectName, //Name of Object + objectColor, // Color of Object + isConstraint, //Boolean value that alters text for each row. + updateHandler, // Function that handles updates the attribute Array + deleteHandler // Function that handles removing attributes from attribute Array +})=>{ + //Local state tracking and setting name and val of Attributes as well as whether or not attribute already existed prior to Task input. const [attributeName, setAttributeName] = useState(""); const [attributeVal, setAttributeVal] = useState(true) const [isExistingAttribute, setIsExistingAttribute] = useState(false) - const nameRef = useRef(); + //Sets Starting values of local state + useEffect(()=>{ + setAttributeName(attribute.name); + setAttributeVal(attribute.val); + },[]) + //Updates Local and Payload state with changes to object name attribute const changeHandler = e=>{ e.preventDefault() - + setAttributeName(e.target.value) + updateHandler({...attribute, name:e.target.value, val:attributeVal}) } + //Sets Val attribute to true const trueHandler = ()=>{ setAttributeVal(true) + updateHandler({...attribute, name:attributeName, val:true}) } + //Sets Val attribute to true const falseHandler = ()=>{ setAttributeVal(false) + updateHandler({...attribute, name:attributeName, val:false}) } useEffect(()=>{ @@ -28,15 +46,11 @@ const AttributeRow = ({attribute, objectName, objectColor, isConstraint, deleteH return(
- {isConstraint? -

{objectName.toUpperCase()}

- : -

Now the {objectName.toUpperCase()}

- } +

{isConstraint ? "": " Now the "}{objectName.toUpperCase()}

- + {isExistingAttribute ?
: }
) } diff --git a/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/index.js b/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/index.js index 36c52a5ae..3cae948ea 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/index.js +++ b/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/index.js @@ -1,46 +1,81 @@ +//REACT import React, {useState, useEffect} from "react"; - +//STYLING import "./styles.css" - +//CUSTOM COMPONENTS import AttributeRow from "./AttributeRow" - - -const AttributeSetter = ({header, objectName, objectColor, attributes, isConstraint}) => { +import InfoToolTip from "../InfoToolTip"; +//ICONS +import { AiOutlinePlus } from "react-icons/ai"; +//Allows user to add and set value of attributes +const AttributeSetter = ({ + header, // Label of setter + objectName, // Name of object to whom the Attributes belong + objectColor, // Text color of object + attributes, // Array of attributes taken from target object + isConstraint, // Formats component for constraint case where attributes in place cannot be changed but can still be removed. + setter, // The setState function that connects the values in Attributes Array to the payload being submitted + toolTipCopy, // Copy for relevant tooltip + hasToolTip // signifies whether the component has a tooltip or not. +}) => { + //Local State for Attributes being listed and changed in component const [attributeList, setAttributeList] = useState([]) + //Sets is existing attribute for attributes that already exist before interaction useEffect(()=>{ if(attributes){ const existingAttributes = attributes.map(att => ({...att, isExisting:true})) setAttributeList(existingAttributes) } - },[attributes]) + },[]) + //Function used to add a "blank attribute to the array." const addAttributeHandler = ()=>{ const newAtt = [...attributeList, {name:"", val:true}] setAttributeList(newAtt) + setter(newAtt) } + //Function used to update attributes + const updateAttributeHandler = (updateIndex, update) => { + let filteredListPreUpdate = attributeList.filter((item, index)=> (index !== updateIndex))//Unupdated entry removed from array + let updatedArr = [...filteredListPreUpdate, update] // Updated Entry added to array + setAttributeList(updatedArr) // Array with updated entry set to local state + setter(updatedArr) // Array with updated entry set to payload state + } + //Function used to remove Attributes const removeAttributeHandler = (deletedIndex)=>{ let updatedArr = attributeList.filter((item, index)=> (index !== deletedIndex)) setAttributeList(updatedArr) + setter(updatedArr) } return (
-
-
-
-

- {objectName.toUpperCase()} {header} -

-
-
-

- + -

+ +
+
+
+

+ + {objectName.toUpperCase()} + {header} +

+
+
+ +
-
+
{ attributeList.length ? attributeList.map((att, index)=>( - removeAttributeHandler(index)}/> + updateAttributeHandler(index, update)} + deleteHandler={()=>removeAttributeHandler(index)} + /> )) : null diff --git a/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/styles.css b/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/styles.css index c84f5609b..bf113b13f 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/styles.css +++ b/crowdsourcing/constraint_events/webapp/src/components/AttributeSetter/styles.css @@ -1,13 +1,16 @@ +/* ATTRIBUTE SETTER STYLES */ .setter-container { display:flex; flex-direction: column; - justify-content: flex-start; - align-items: flex-start; + justify-content: center; + align-items: center; + margin-top:10px; + margin-bottom: 10px; } .setter-header { display:flex; flex-direction: row; - justify-content: flex-start; + justify-content: center; align-items: center; width: 100%; margin: 5px; @@ -21,7 +24,7 @@ font-weight: bold; font-size: 20px; } - +/*Button styles*/ .button-container { width: 40px; height: 40px; @@ -37,7 +40,7 @@ font-size: 24px; } .button-text{ - + margin-bottom: 0px; } .button-container:hover { cursor: pointer; @@ -48,23 +51,23 @@ box-shadow: 0px 0px 0px black; } /* Attribute Row */ -.attributerow-container{ +.attributerow-container { display: flex; flex-direction: row; - justify-content: flex-start; + justify-content: center; align-items: center; - margin: 5px 0px 5px 20px; + margin: 5px 0px 5px 0px; width: 100%; - } .attribute-label__container { - + width:30%; } .attribute-label__text { - text-align: left; + text-align: center; font-size: 20px; + margin-bottom: 0px; } .name-text { @@ -74,6 +77,7 @@ border-style: ridge; padding: 3px; border-width: 4px; + width:80%; } .value-container{ display:flex; @@ -82,12 +86,10 @@ align-items: center; margin-left:4px; margin-right: 6px; - width: 30%; + width: 50%; } -.attribute-container{ - width: 50; -} +.attribute-container{} .attribute-icon { font-size: 50px; diff --git a/crowdsourcing/constraint_events/webapp/src/components/Checkbox/index.js b/crowdsourcing/constraint_events/webapp/src/components/Checkbox/index.js new file mode 100644 index 000000000..daee1c924 --- /dev/null +++ b/crowdsourcing/constraint_events/webapp/src/components/Checkbox/index.js @@ -0,0 +1,22 @@ +//REACT +import React from "react"; +//STYLING +import "./styles.css" +//ICONS +import { ImCheckboxChecked } from "react-icons/im"; +import { ImCheckboxUnchecked } from "react-icons/im"; +//Checkbox - takes completion condition as isComplete prop and displays either checked or unchecked. +const Checkbox = ({isComplete}) => { + return ( +
+ { + isComplete ? + + : + + } +
+ ); +} + +export default Checkbox ; diff --git a/crowdsourcing/constraint_events/webapp/src/components/Checkbox/styles.css b/crowdsourcing/constraint_events/webapp/src/components/Checkbox/styles.css new file mode 100644 index 000000000..378d60c89 --- /dev/null +++ b/crowdsourcing/constraint_events/webapp/src/components/Checkbox/styles.css @@ -0,0 +1,5 @@ +/* CHECKBOX STYLES */ +.checkbox-container { + margin-left: 6px; + margin-right: 4px; +} diff --git a/crowdsourcing/constraint_events/webapp/src/components/DropdownSelect/index.js b/crowdsourcing/constraint_events/webapp/src/components/DropdownSelect/index.js index 8e479514b..41a362a53 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/DropdownSelect/index.js +++ b/crowdsourcing/constraint_events/webapp/src/components/DropdownSelect/index.js @@ -1,20 +1,25 @@ +//REACT import React, {useState} from "react"; -import Dropdown from 'react-bootstrap/Dropdown' - +//STYLING import "./styles.css" - -const DropdownSelect = ({options})=>{ +//DropdownSelect - takes options and function connecting it to payload state as props +const DropdownSelect = ({options, selectFunction})=>{ + // local state for the selected value in the dropdown const [selectedOption, setSelectedOption]= useState("Select Location") + //State to disable default option which has no value upon user interacting with dropdown + const [firstSelect, setFirstSelect] =useState(false) + //Upon selection updates both the localstate and the payload state. const selectHandler = (selection)=>{ - setSelectedOption(selection) + setFirstSelect(true) + setSelectedOption(selection.value) + selectFunction(selection) } - console.log("OPTIONS", options) return(
- + { - options.map((option, index) =>) + options.map((option, index) =>) }
diff --git a/crowdsourcing/constraint_events/webapp/src/components/DropdownSelect/styles.css b/crowdsourcing/constraint_events/webapp/src/components/DropdownSelect/styles.css index 32e2ce282..f703b5200 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/DropdownSelect/styles.css +++ b/crowdsourcing/constraint_events/webapp/src/components/DropdownSelect/styles.css @@ -1,3 +1,4 @@ +/* DROPDOWN STYLES */ .dropdown-container { } diff --git a/crowdsourcing/constraint_events/webapp/src/components/ErrorToast/index.js b/crowdsourcing/constraint_events/webapp/src/components/ErrorToast/index.js new file mode 100644 index 000000000..99bbeebe7 --- /dev/null +++ b/crowdsourcing/constraint_events/webapp/src/components/ErrorToast/index.js @@ -0,0 +1,31 @@ +//REACT +import React, {useState} from "react"; +//STYLING +import "./styles.css" +//BOOTSTRAP COMPONENTS +import Alert from 'react-bootstrap/Alert' +// ErrorToast - renders toast for user with relevant errors rendered as a bulleted list in the toast. +const ErrorToast= ({ + errors, // Array of Errors from App state generated when user attempts an erroneous submission + showError, //Boolean signaling toast to render. + hideError, // Function that will hide toast upon clicking the x in the top right corner +})=>{ + return( + +

+ ERROR +

+
    + { + errors.map((err, index)=>( +
  • + {err} +
  • + )) + } +
+ +
+ )} + +export default ErrorToast diff --git a/crowdsourcing/constraint_events/webapp/src/components/ErrorToast/styles.css b/crowdsourcing/constraint_events/webapp/src/components/ErrorToast/styles.css new file mode 100644 index 000000000..d16bdd4f6 --- /dev/null +++ b/crowdsourcing/constraint_events/webapp/src/components/ErrorToast/styles.css @@ -0,0 +1,36 @@ +/* ERROR TOAST STYLES */ +ul{ + list-style: none; +} + +ul li::before { + content: "\2022"; + color: white; + font-weight: bold; + display: inline-block; + width: 3em; + margin-left: 1em; + } + +.toast-container{ + position: fixed; + background-color: red; + display: flex; + flex-direction: column; +} +.toast-header{ + color:white; + background-color: red; + font-weight: bold; + width: 90vw; +} +.toast-text{ + text-align: center; + font-weight: bold; +} + +.toast-error{ + color: white; + text-align: left; + width: 90vw; +} diff --git a/crowdsourcing/constraint_events/webapp/src/components/Header/index.js b/crowdsourcing/constraint_events/webapp/src/components/Header/index.js index 0964603a1..c92294331 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/Header/index.js +++ b/crowdsourcing/constraint_events/webapp/src/components/Header/index.js @@ -1,7 +1,8 @@ +//REACT import React from "react"; - +//STYLES import "./styles.css" - +// header - renders task header const Header = () => { return (
diff --git a/crowdsourcing/constraint_events/webapp/src/components/Header/styles.css b/crowdsourcing/constraint_events/webapp/src/components/Header/styles.css index 79f8fad64..0222484af 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/Header/styles.css +++ b/crowdsourcing/constraint_events/webapp/src/components/Header/styles.css @@ -1,3 +1,4 @@ +/* HEADER STYLES */ .header { width: 100%; display: flex; diff --git a/crowdsourcing/constraint_events/webapp/src/components/InfoToolTip/Tip.js b/crowdsourcing/constraint_events/webapp/src/components/InfoToolTip/Tip.js new file mode 100644 index 000000000..3a540acbd --- /dev/null +++ b/crowdsourcing/constraint_events/webapp/src/components/InfoToolTip/Tip.js @@ -0,0 +1,17 @@ +//REACT +import React from "react"; + +// Tip - html rendered by tool tip +const Tip = ({ + tutorialCopy//Text that will be displayed in the tool tip +})=>{ + return ( +
+

+ {tutorialCopy} +

+
+ ) +} + +export default Tip ; diff --git a/crowdsourcing/constraint_events/webapp/src/components/InfoToolTip/index.js b/crowdsourcing/constraint_events/webapp/src/components/InfoToolTip/index.js new file mode 100644 index 000000000..e545439a4 --- /dev/null +++ b/crowdsourcing/constraint_events/webapp/src/components/InfoToolTip/index.js @@ -0,0 +1,43 @@ +//REACT +import React, {useEffect, useState} from "react"; +//STYLING +import "./styles.css"; +//REACT TIPPY +import {Tooltip} from 'react-tippy'; +// ICONS +import { BsInfoCircle } from "react-icons/bs"; +//CUSTOM COMPONENTS +import Tip from "./Tip" + + +// InfoToolTip - renders an info icon that will display tooltip copy provided by props if has tooltip boolean value is true. Other wise it will plainly render the child component +const InfoToolTip = ({ + hasToolTip,//Boolean value stating whether child component has tool tip or not. + tutorialCopy, // Tool Tip Copy passed as string + children // JSX next to tooltip +})=>{ + + if(hasToolTip){ + return ( +
+
+ {children} +
+ + } + position="left" + theme="dark" + size="big" + > + + +
+ )}else{ + return <>{children} + } +} + + +export default InfoToolTip ; diff --git a/crowdsourcing/constraint_events/webapp/src/components/InfoToolTip/styles.css b/crowdsourcing/constraint_events/webapp/src/components/InfoToolTip/styles.css new file mode 100644 index 000000000..35b7fe611 --- /dev/null +++ b/crowdsourcing/constraint_events/webapp/src/components/InfoToolTip/styles.css @@ -0,0 +1,33 @@ +/* INFO TOOL TIP STYLES */ +.info-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +/* TIP */ +.tip-container{ + height: 100%; + width: 100%; + z-index: 100; + background-color: black; + border-radius: 15px; +} +.tip-text { + font-size: 16px; + color:white; + padding:10px; + margin:0; +} + +.info-icon{ + color: black; + font-size: 30px; + margin-left:6px; + margin-right:10px; +} + +.info-icon:hover { + color: blue; +} diff --git a/crowdsourcing/constraint_events/webapp/src/components/LoadingScreen/index.js b/crowdsourcing/constraint_events/webapp/src/components/LoadingScreen/index.js index 641741770..684573796 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/LoadingScreen/index.js +++ b/crowdsourcing/constraint_events/webapp/src/components/LoadingScreen/index.js @@ -1,8 +1,9 @@ +//REACT import React from "react"; - +//STYLING import "./styles.css" - +// loadingScreen - what will be rendered when mephisto isLoading prop is true const LoadingScreen = ()=> { return (
diff --git a/crowdsourcing/constraint_events/webapp/src/components/LoadingScreen/styles.css b/crowdsourcing/constraint_events/webapp/src/components/LoadingScreen/styles.css index e69de29bb..85b7e5258 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/LoadingScreen/styles.css +++ b/crowdsourcing/constraint_events/webapp/src/components/LoadingScreen/styles.css @@ -0,0 +1 @@ +/* LOADING SCREEN STYLES*/ diff --git a/crowdsourcing/constraint_events/webapp/src/components/Questions/BooleanQuestion/index.js b/crowdsourcing/constraint_events/webapp/src/components/Questions/BooleanQuestion/index.js index ff316c759..2aa7154a9 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/Questions/BooleanQuestion/index.js +++ b/crowdsourcing/constraint_events/webapp/src/components/Questions/BooleanQuestion/index.js @@ -1,27 +1,54 @@ +//REACT import React, {useEffect, useState} from "react"; +//STYLING import "./styles.css"; - //CUSTOM COMPONENTS -import TaskButton from "../../TaskButton" -import FormQuestion from "../../Utils/FormatQuestion"; - -const BooleanQuestion = ({question, trueAnswer, falseAnswer, keywords, children})=>{ +import TaskButton from "../../TaskButton"; +import FormatQuestion from "../../Utils/FormatQuestion"; +import InfoToolTip from "../../InfoToolTip"; +import Checkbox from "../../Checkbox"; +//BooleanQuestion - a component that renders a question that expects a boolean answer. If a component is wrapped by this component under the "true" answer it will conditionally render that child. +const BooleanQuestion = ({ + question, // The Question posed by the question + trueAnswer, //Object that will have a name attribute which will be the text rendered on the "true" button + falseAnswer,//Object that will have a name attribute which will be the text rendered on the "false" button + keywords, //key words will replace any # in the header text being formatted by the formatQuestion function + inverted,// Will render children based on a false answer rather that true if this value is true + formFunction,// setState function that connects the answer to the payload state + toolTipCopy,// Copy for desired tooltip + hasToolTip,// Boolean stating whether or not this component has a tooltip + isComplete,// Completion condition for question to be satisfactorily answered + children +})=>{ + //local state for question answer const [answer, setAnswer] = useState(null); + //sets both local and payload state to true const trueHandler = ()=>{ setAnswer(true) + formFunction(true) } + //sets both local and payload state to false const falseHandler = ()=>{ setAnswer(false) + formFunction(false) } return(
- + +
+ {hasToolTip?:null} + +
+
-
- { - answer && children ? +
+ { //if inverted it will only render child if answer is false not if answer == null + ((!!inverted ? (!answer && answer!==null) : answer) && children) ? children : null diff --git a/crowdsourcing/constraint_events/webapp/src/components/Questions/BooleanQuestion/styles.css b/crowdsourcing/constraint_events/webapp/src/components/Questions/BooleanQuestion/styles.css index 895958b0b..bfea6c5c4 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/Questions/BooleanQuestion/styles.css +++ b/crowdsourcing/constraint_events/webapp/src/components/Questions/BooleanQuestion/styles.css @@ -1,9 +1,12 @@ +/* BOOLEAN QUESTION STYLES */ .booleanquestion-container { display: flex; justify-content: flex-start; - width: 100%; + width: 90%; align-items: flex-start; flex-direction: column; + margin-top:10px; + margin-bottom: 10px; } .booleanquestion-text { @@ -18,4 +21,5 @@ justify-content: flex-start; align-items: center; padding-left: 30px; + width: 100%; } diff --git a/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/FieldRow.js b/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/FieldRow.js index 16464e7c1..bd25a8f95 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/FieldRow.js +++ b/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/FieldRow.js @@ -1,43 +1,61 @@ -import React, {useEffect, useRef} from "react"; - +//REACT +import React, {useState} from "react"; //CUSTOM COMPONENTS import DropdownSelect from "../../DropdownSelect"; - -const FieldRow = ({field, dropdown, options})=>{ - const answerRef = useRef(); +// fieldrow - row with styled form label and input to the right of it. Input is "fed" into field question. This is for question with multiple short inputs. +const FieldRow = ({ + field, //Object with name and value attributes + dropdown, // boolean value signalling whether field is a dropdown or not + options, // Options in the dropdown an array of objects which have name and val attributes + changeFunction, //function that updates the field question state +})=>{ + //field local state + const [fieldVal, setFieldVal] = useState("") + //updates state in field question component const changeHandler = e=>{ e.preventDefault() - + setFieldVal(e.target.value) + changeFunction(e.target.value) } - + // if dropdown is true renders field with dropdown input if(dropdown){ - return(
-
-

- {field} -

+
+
+

+ {field.name.toUpperCase()} +

+
-
- +
+
) }else{ + //if dropdown if false renders field with text input return(
-
-

- {field} -

+
+
+

+ {field.name.toUpperCase()} +

+
-
- +
+
+ +
) diff --git a/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/index.js b/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/index.js index 5a03ca820..8b4156898 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/index.js +++ b/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/index.js @@ -1,19 +1,37 @@ -import React, {useEffect, useRef} from "react"; - +//REACT +import React from "react"; +//STYLING import "./styles.css" - //CUSTOM COMPONENTS import FieldRow from "./FieldRow"; -const FieldQuestion = ({question, fields})=>{ - const answerRef = useRef(); +// FieldQuestion - a question who's answer is a form with multiple fields. these fields can be text input or dropdown. +const FieldQuestion = ({ + question,//question text + fields,//an array of objects with a string name attribute, a dropdown boolean attribute, and if boolean is true an array of options for the dropdown + formFunction,// function that connects question answer to payload state. + formState, // payload state +})=>{ + //updates payload state with for inputs + const ChangeHandler = (formName, formVal)=>{ + let updatedState = {...formState, [formName]:formVal} + formFunction(updatedState) + } return(

{question}

{ fields ? - fields.map((field, index)=>) + fields.map((field, index)=>( + ChangeHandler(field.name, val)} + /> + )) :null }
diff --git a/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/styles.css b/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/styles.css index 261a7dd1c..041eefebc 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/styles.css +++ b/crowdsourcing/constraint_events/webapp/src/components/Questions/FieldQuestion/styles.css @@ -1,9 +1,12 @@ +/* FIELD QUESTION STYLES */ .fieldquestion-container { display: flex; justify-content: center; width: 100%; flex-direction: column; - align-items: center; + align-items: flex-start; + margin-top:10px; + margin-bottom: 10px; } .fieldquestion-header { @@ -27,7 +30,13 @@ flex-direction: row; justify-content: flex-end; align-items: center; + flex-wrap: wrap; +} +.fieldrow-container__section--half { + display:flex; + width:50%; } + .field-container { background-color: green; border-radius: 5px; @@ -40,6 +49,7 @@ color: white; font-weight: bold; font-size: 20px; + margin-bottom: 0; } .fieldanswer-container { @@ -52,4 +62,5 @@ border-style: ridge; padding: 3px; border-width: 4px; + margin-bottom: 0; } diff --git a/crowdsourcing/constraint_events/webapp/src/components/Questions/FormQuestion/index.js b/crowdsourcing/constraint_events/webapp/src/components/Questions/FormQuestion/index.js index 0194dd84f..6e14b22d9 100644 --- a/crowdsourcing/constraint_events/webapp/src/components/Questions/FormQuestion/index.js +++ b/crowdsourcing/constraint_events/webapp/src/components/Questions/FormQuestion/index.js @@ -1,25 +1,49 @@ +//REACT import React, {useEffect, useState, useRef} from "react"; - +//STYLES import "./styles.css" +//CUSTOM COMPONENT +import InfoToolTip from "../../InfoToolTip"; +import Checkbox from "../../Checkbox"; - -const FormQuestion = ({question, placeholder, questionColor, upperCaseQuestion, formVal, formFunction})=>{ +//FormQuestion - a question who's answer is gathered from simple text input +const FormQuestion = ({ + question,//Actual text of question + placeholder,// Placeholder if needed + questionColor, //Text color + upperCaseQuestion, // boolean that makes question entirely uppercase + formVal,// Initial form value + formFunction,// setState function that connects answer to payload state + toolTipCopy,// Text that will appear in tooltip + hasToolTip,// boolean that adds tooltip icon and text if true + isComplete// completion condition +})=>{ + //local state of form const [description, setDescription] = useState("") + //Sets initial value on render useEffect(()=>{ setDescription(formVal) },[]) + //Updates both local and payload state with answer const changeHandler = e=>{ e.preventDefault() setDescription(e.target.value) - + formFunction(e.target.value) } return(
-

- {upperCaseQuestion ? question.toUpperCase() : question} -

+ +
+ {hasToolTip ? :null} +

+ {upperCaseQuestion ? question.toUpperCase() : question} +

+
+

-