From 260d555986c50376743f9a67a5afac398d5112b9 Mon Sep 17 00:00:00 2001 From: jsomeara Date: Sun, 8 Sep 2024 23:05:36 -0700 Subject: [PATCH 01/24] Auto resize for explore page --- app/controllers/AuditController.scala | 442 ++++++++++-------- app/views/explore.scala.html | 6 - app/views/exploreWrapper.scala.html | 11 + public/javascripts/SVLabel/css/svl.css | 4 + .../javascripts/SVLabel/src/SVLabel/Main.js | 11 +- .../src/SVLabel/onboarding/Onboarding.js | 2 +- public/javascripts/common/wrapperFrame.js | 22 + public/stylesheets/wrapperFrame.css | 14 + 8 files changed, 293 insertions(+), 219 deletions(-) create mode 100644 app/views/exploreWrapper.scala.html create mode 100644 public/javascripts/common/wrapperFrame.js create mode 100644 public/stylesheets/wrapperFrame.css diff --git a/app/controllers/AuditController.scala b/app/controllers/AuditController.scala index 8429e0305c..bd4f5e8cd9 100644 --- a/app/controllers/AuditController.scala +++ b/app/controllers/AuditController.scala @@ -43,123 +43,143 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth } /** - * Returns an explore page. + * Returns /explore wrapper page. */ - def explore(newRegion: Boolean, retakeTutorial: Option[Boolean], routeId: Option[Int], resumeRoute: Boolean) = UserAwareAction.async { implicit request => + def exploreWrapper = UserAwareAction.async { implicit request => val timestamp: Timestamp = new Timestamp(Instant.now.toEpochMilli) val ipAddress: String = request.remoteAddress - val qString = request.queryString.map { case (k, v) => k.mkString -> v.mkString } - - val studyGroupInput: Option[String] = qString.get("studyGroup") - val studyGroup: String = studyGroupInput.map(g => if (g == "1" || g == "2") g else "").getOrElse("") - - val retakingTutorial: Boolean = retakeTutorial.isDefined && retakeTutorial.get request.identity match { case Some(user) => - // If the user is a Turker and has audited less than 50 meters in the current region, then delete the current region. - val currentMeters: Option[Float] = MissionTable.getMetersAuditedInCurrentMission(user.userId) - if (user.role.getOrElse("") == "Turker" && currentMeters.isDefined && currentMeters.get < 50) { - UserCurrentRegionTable.delete(user.userId) - } + WebpageActivityTable.save(WebpageActivity(0, user.userId.toString, ipAddress, "Visit_Explore_Wrapper", timestamp)) + Future.successful(Ok(views.html.exploreWrapper("Project Sidewalk - Audit", Some(user)))) + case None => + Future.successful(Redirect("/anonSignUp?url=/explore")) + } + } - // Check if user has an active route or create a new one if routeId was supplied. If resumeRoute is false and no - // routeId was supplied, then the function should return None and the user is not sent on a specific route. - val userRoute: Option[UserRoute] = UserRouteTable.setUpPossibleUserRoute(routeId, user.userId, resumeRoute) - val route: Option[Route] = userRoute.flatMap(ur => RouteTable.getRoute(ur.routeId)) - - // If user is on a specific route, assign them to the correct region. If they have no region assigned or - // newRegion is set to true, assign a new region. Otherwise, get their previously assigned region. - var region: Option[Region] = - if (route.isDefined) { - val regionId: Int = UserCurrentRegionTable.saveOrUpdate(user.userId, route.get.regionId) - RegionTable.getRegion(regionId) - } else if (newRegion || !UserCurrentRegionTable.isAssigned(user.userId)) { - UserCurrentRegionTable.assignRegion(user.userId) - } else { - RegionTable.getCurrentRegion(user.userId) + /** + * Returns an explore page. + */ + def explore(newRegion: Boolean, retakeTutorial: Option[Boolean], routeId: Option[Int], resumeRoute: Boolean) = UserAwareAction.async { implicit request => + if (request.headers.get("Sec-Fetch-Dest").getOrElse("") == "document") { + exploreWrapper.apply(request) + } else { + val timestamp: Timestamp = new Timestamp(Instant.now.toEpochMilli) + val ipAddress: String = request.remoteAddress + val qString = request.queryString.map { case (k, v) => k.mkString -> v.mkString } + + val studyGroupInput: Option[String] = qString.get("studyGroup") + val studyGroup: String = studyGroupInput.map(g => if (g == "1" || g == "2") g else "").getOrElse("") + + val retakingTutorial: Boolean = retakeTutorial.isDefined && retakeTutorial.get + + request.identity match { + case Some(user) => + // If the user is a Turker and has audited less than 50 meters in the current region, then delete the current region. + val currentMeters: Option[Float] = MissionTable.getMetersAuditedInCurrentMission(user.userId) + if (user.role.getOrElse("") == "Turker" && currentMeters.isDefined && currentMeters.get < 50) { + UserCurrentRegionTable.delete(user.userId) } - // Log visit to the Explore page. - val activityStr: String = - if (route.isDefined) s"Visit_Audit_RouteId=${route.get.routeId}" - else if (newRegion) "Visit_Audit_NewRegionSelected" - else "Visit_Audit" - WebpageActivityTable.save(WebpageActivity(0, user.userId.toString, ipAddress, activityStr, timestamp)) - - // Check if a user still has tasks available in this region. This also should never really happen. - if (route.isDefined && region.isEmpty) { - Logger.error("Unable to assign a region for the route.") - } else if (region.isEmpty || !AuditTaskTable.isTaskAvailable(user.userId, region.get.regionId)) { - region = UserCurrentRegionTable.assignRegion(user.userId) - } else if (region.isEmpty) { - Logger.error("Unable to assign a region to a user.") // This should _really_ never happen. - } + // Check if user has an active route or create a new one if routeId was supplied. If resumeRoute is false and no + // routeId was supplied, then the function should return None and the user is not sent on a specific route. + val userRoute: Option[UserRoute] = UserRouteTable.setUpPossibleUserRoute(routeId, user.userId, resumeRoute) + val route: Option[Route] = userRoute.flatMap(ur => RouteTable.getRoute(ur.routeId)) + + // If user is on a specific route, assign them to the correct region. If they have no region assigned or + // newRegion is set to true, assign a new region. Otherwise, get their previously assigned region. + var region: Option[Region] = + if (route.isDefined) { + val regionId: Int = UserCurrentRegionTable.saveOrUpdate(user.userId, route.get.regionId) + RegionTable.getRegion(regionId) + } else if (newRegion || !UserCurrentRegionTable.isAssigned(user.userId)) { + UserCurrentRegionTable.assignRegion(user.userId) + } else { + RegionTable.getCurrentRegion(user.userId) + } - val regionId: Int = region.get.regionId - - val role: String = user.role.getOrElse("") - val payPerMeter: Double = if (role == "Turker") AMTAssignmentTable.TURKER_PAY_PER_METER else AMTAssignmentTable.VOLUNTEER_PAY - val tutorialPay: Double = - if (retakingTutorial || role != "Turker") AMTAssignmentTable.VOLUNTEER_PAY - else AMTAssignmentTable.TURKER_TUTORIAL_PAY - - val missionSetProgress: MissionSetProgress = - if (role == "Turker") MissionTable.getProgressOnMissionSet(user.username) - else MissionTable.defaultAuditMissionSetProgress - - var mission: Mission = - if (retakingTutorial) MissionTable.resumeOrCreateNewAuditOnboardingMission(user.userId, tutorialPay).get - else MissionTable.resumeOrCreateNewAuditMission(user.userId, regionId, payPerMeter, tutorialPay).get - - // If there is a partially completed task in this route or mission, get that, o/w make a new one. - val task: Option[NewTask] = - if (MissionTypeTable.missionTypeIdToMissionType(mission.missionTypeId) == "auditOnboarding") { - Some(AuditTaskTable.getATutorialTask(mission.missionId)) - } else if (route.isDefined) { - UserRouteTable.getRouteTask(userRoute.get, mission.missionId) - } else if (mission.currentAuditTaskId.isDefined) { - val currTask: Option[NewTask] = AuditTaskTable.selectTaskFromTaskId(mission.currentAuditTaskId.get) - // If we found no task with the given ID, try to get any new task in the neighborhood. - if (currTask.isDefined) currTask - else AuditTaskTable.selectANewTaskInARegion(regionId, user.userId, mission.missionId) - } else { - AuditTaskTable.selectANewTaskInARegion(regionId, user.userId, mission.missionId) + // Log visit to the Explore page. + val activityStr: String = + if (route.isDefined) s"Visit_Audit_RouteId=${route.get.routeId}" + else if (newRegion) "Visit_Audit_NewRegionSelected" + else "Visit_Audit" + WebpageActivityTable.save(WebpageActivity(0, user.userId.toString, ipAddress, activityStr, timestamp)) + + // Check if a user still has tasks available in this region. This also should never really happen. + if (route.isDefined && region.isEmpty) { + Logger.error("Unable to assign a region for the route.") + } else if (region.isEmpty || !AuditTaskTable.isTaskAvailable(user.userId, region.get.regionId)) { + region = UserCurrentRegionTable.assignRegion(user.userId) + } else if (region.isEmpty) { + Logger.error("Unable to assign a region to a user.") // This should _really_ never happen. } - val nextTempLabelId: Int = LabelTable.nextTempLabelId(user.userId) - // If the mission has the wrong audit_task_id, update it. - if (task.isDefined && task.get.auditTaskId != mission.currentAuditTaskId) { - MissionTable.updateAuditProgressOnly(user.userId, mission.missionId, mission.distanceProgress.getOrElse(0F), task.get.auditTaskId) - mission = MissionTable.getMission(mission.missionId).get - } + val regionId: Int = region.get.regionId - // Check if they have already completed an explore mission. We send them to /validate after their first explore - // mission, but only after every third explore mission after that. - val completedMissions: Boolean = MissionTable.countCompletedMissions(user.userId, missionType = "audit") > 0 - - val tutorialStreetId: Int = ConfigTable.getTutorialStreetId - val lang: Lang = Configs.getLangFromRequest(request) - val cityInfo: List[CityInfo] = Configs.getAllCityInfo(lang) - val cityId: String = cityInfo.filter(_.current).head.cityId - val makeCrops: Boolean = ConfigTable.getMakeCrops - if (missionSetProgress.missionType != "audit") { - Future.successful(Redirect("/validate")) - } else { - // On the crowdstudy server, we want to assign users to a study group. - val response = Ok(views.html.explore("Project Sidewalk - Audit", task, mission, region.get, userRoute, missionSetProgress.numComplete, completedMissions, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops)) - if (cityId == "crowdstudy" && studyGroup.nonEmpty) Future.successful(response.withCookies(Cookie("SIDEWALK_STUDY_GROUP", studyGroup, httpOnly = false))) - else Future.successful(response) - } - // For anonymous users. - case None => - // UTF-8 codes needed to pass a URL that contains parameters: ? is %3F, & is %26 - val routeParam: String = routeId.map(rId => s"%3FrouteId=$rId").getOrElse("") - val studyGroupParam: String = - if (routeParam.nonEmpty && studyGroup.nonEmpty) s"%26studyGroup=$studyGroup" - else if (studyGroup.nonEmpty) s"%3FstudyGroup=$studyGroup" - else "" - Future.successful(Redirect("/anonSignUp?url=/explore" + routeParam + studyGroupParam)) + val role: String = user.role.getOrElse("") + val payPerMeter: Double = if (role == "Turker") AMTAssignmentTable.TURKER_PAY_PER_METER else AMTAssignmentTable.VOLUNTEER_PAY + val tutorialPay: Double = + if (retakingTutorial || role != "Turker") AMTAssignmentTable.VOLUNTEER_PAY + else AMTAssignmentTable.TURKER_TUTORIAL_PAY + + val missionSetProgress: MissionSetProgress = + if (role == "Turker") MissionTable.getProgressOnMissionSet(user.username) + else MissionTable.defaultAuditMissionSetProgress + + var mission: Mission = + if (retakingTutorial) MissionTable.resumeOrCreateNewAuditOnboardingMission(user.userId, tutorialPay).get + else MissionTable.resumeOrCreateNewAuditMission(user.userId, regionId, payPerMeter, tutorialPay).get + + // If there is a partially completed task in this route or mission, get that, o/w make a new one. + val task: Option[NewTask] = + if (MissionTypeTable.missionTypeIdToMissionType(mission.missionTypeId) == "auditOnboarding") { + Some(AuditTaskTable.getATutorialTask(mission.missionId)) + } else if (route.isDefined) { + UserRouteTable.getRouteTask(userRoute.get, mission.missionId) + } else if (mission.currentAuditTaskId.isDefined) { + val currTask: Option[NewTask] = AuditTaskTable.selectTaskFromTaskId(mission.currentAuditTaskId.get) + // If we found no task with the given ID, try to get any new task in the neighborhood. + if (currTask.isDefined) currTask + else AuditTaskTable.selectANewTaskInARegion(regionId, user.userId, mission.missionId) + } else { + AuditTaskTable.selectANewTaskInARegion(regionId, user.userId, mission.missionId) + } + val nextTempLabelId: Int = LabelTable.nextTempLabelId(user.userId) + + // If the mission has the wrong audit_task_id, update it. + if (task.isDefined && task.get.auditTaskId != mission.currentAuditTaskId) { + MissionTable.updateAuditProgressOnly(user.userId, mission.missionId, mission.distanceProgress.getOrElse(0F), task.get.auditTaskId) + mission = MissionTable.getMission(mission.missionId).get + } + + // Check if they have already completed an explore mission. We send them to /validate after their first explore + // mission, but only after every third explore mission after that. + val completedMissions: Boolean = MissionTable.countCompletedMissions(user.userId, missionType = "audit") > 0 + + val tutorialStreetId: Int = ConfigTable.getTutorialStreetId + val lang: Lang = Configs.getLangFromRequest(request) + val cityInfo: List[CityInfo] = Configs.getAllCityInfo(lang) + val cityId: String = cityInfo.filter(_.current).head.cityId + val makeCrops: Boolean = ConfigTable.getMakeCrops + if (missionSetProgress.missionType != "audit") { + Future.successful(Redirect("/validate")) + } else { + // On the crowdstudy server, we want to assign users to a study group. + val response = Ok(views.html.explore("Project Sidewalk - Audit", task, mission, region.get, userRoute, missionSetProgress.numComplete, completedMissions, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops)) + if (cityId == "crowdstudy" && studyGroup.nonEmpty) Future.successful(response.withCookies(Cookie("SIDEWALK_STUDY_GROUP", studyGroup, httpOnly = false))) + else Future.successful(response) + } + // For anonymous users. + case None => + // UTF-8 codes needed to pass a URL that contains parameters: ? is %3F, & is %26 + val routeParam: String = routeId.map(rId => s"%3FrouteId=$rId").getOrElse("") + val studyGroupParam: String = + if (routeParam.nonEmpty && studyGroup.nonEmpty) s"%26studyGroup=$studyGroup" + else if (studyGroup.nonEmpty) s"%3FstudyGroup=$studyGroup" + else "" + Future.successful(Redirect("/anonSignUp?url=/explore" + routeParam + studyGroupParam)) + } } } @@ -167,44 +187,118 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth * Explore a given region. */ def exploreRegion(regionId: Int) = UserAwareAction.async { implicit request => - request.identity match { - case Some(user) => - val userId: UUID = user.userId - val timestamp: Timestamp = new Timestamp(Instant.now.toEpochMilli) - val ipAddress: String = request.remoteAddress - val regionOption: Option[Region] = RegionTable.getRegion(regionId) - WebpageActivityTable.save(WebpageActivity(0, userId.toString, ipAddress, "Visit_Audit", timestamp)) + if (request.headers.get("Sec-Fetch-Dest").getOrElse("") == "document") { + exploreWrapper.apply(request) + } else { + request.identity match { + case Some(user) => + val userId: UUID = user.userId + val timestamp: Timestamp = new Timestamp(Instant.now.toEpochMilli) + val ipAddress: String = request.remoteAddress + val regionOption: Option[Region] = RegionTable.getRegion(regionId) + WebpageActivityTable.save(WebpageActivity(0, userId.toString, ipAddress, "Visit_Audit", timestamp)) + + // Update the currently assigned region for the user. + regionOption match { + case Some(region) => + UserCurrentRegionTable.saveOrUpdate(userId, regionId) + val role: String = user.role.getOrElse("") + val payPerMeter: Double = + if (role == "Turker") AMTAssignmentTable.TURKER_PAY_PER_METER else AMTAssignmentTable.VOLUNTEER_PAY + val tutorialPay: Double = + if (role == "Turker") AMTAssignmentTable.TURKER_TUTORIAL_PAY else AMTAssignmentTable.VOLUNTEER_PAY + val mission: Mission = + MissionTable.resumeOrCreateNewAuditMission(userId, regionId, payPerMeter, tutorialPay).get + + val missionSetProgress: MissionSetProgress = + if (role == "Turker") MissionTable.getProgressOnMissionSet(user.username) + else MissionTable.defaultAuditMissionSetProgress + + // If there is a partially completed task in this mission, get that, o/w make a new one. + val task: Option[NewTask] = + if (MissionTypeTable.missionTypeIdToMissionType(mission.missionTypeId) == "auditOnboarding") + Some(AuditTaskTable.getATutorialTask(mission.missionId)) + else if (mission.currentAuditTaskId.isDefined) + AuditTaskTable.selectTaskFromTaskId(mission.currentAuditTaskId.get) + else + AuditTaskTable.selectANewTaskInARegion(regionId, user.userId, mission.missionId) + val nextTempLabelId: Int = LabelTable.nextTempLabelId(userId) + + // Check if they have already completed an audit mission. We send them to /validate after their first audit. + // mission, but only after every third explore mission after that. + val completedMission: Boolean = MissionTable.countCompletedMissions(user.userId, missionType = "audit") > 0 + + val lang: Lang = Configs.getLangFromRequest(request) + val cityInfo: List[CityInfo] = Configs.getAllCityInfo(lang) + val tutorialStreetId: Int = ConfigTable.getTutorialStreetId + val makeCrops: Boolean = ConfigTable.getMakeCrops + if (missionSetProgress.missionType != "audit") { + Future.successful(Redirect("/validate")) + } else { + Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", task, mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops))) + } + case None => + Logger.error(s"Tried to explore region $regionId, but there is no neighborhood with that id.") + Future.successful(Redirect("/explore")) + } + + case None => + Future.successful(Redirect(s"/anonSignUp?url=/explore/region/$regionId")) + } + } + } - // Update the currently assigned region for the user. - regionOption match { - case Some(region) => + /** + * Explore a given street. Optionally, a researcher can be placed at a specific lat/lng or panorama. + */ + def exploreStreet(streetEdgeId: Int, lat: Option[Double], lng: Option[Double], panoId: Option[String]) = UserAwareAction.async { implicit request => + if (request.headers.get("Sec-Fetch-Dest").getOrElse("") == "document") { + exploreWrapper.apply(request) + } else { + val startAtPano: Boolean = panoId.isDefined + val startAtLatLng: Boolean = lat.isDefined && lng.isDefined + request.identity match { + case Some(user) => + val userId: UUID = user.userId + val regionOption: Option[Region] = StreetEdgeRegionTable.getNonDeletedRegionFromStreetId(streetEdgeId) + + if (regionOption.isEmpty) { + Logger.error(s"Either there is no region associated with street edge $streetEdgeId, or it is not a valid id.") + Future.successful(Redirect("/explore")) + } else { + val region: Region = regionOption.get + val regionId: Int = region.regionId UserCurrentRegionTable.saveOrUpdate(userId, regionId) + val role: String = user.role.getOrElse("") val payPerMeter: Double = if (role == "Turker") AMTAssignmentTable.TURKER_PAY_PER_METER else AMTAssignmentTable.VOLUNTEER_PAY val tutorialPay: Double = if (role == "Turker") AMTAssignmentTable.TURKER_TUTORIAL_PAY else AMTAssignmentTable.VOLUNTEER_PAY - val mission: Mission = + var mission: Mission = MissionTable.resumeOrCreateNewAuditMission(userId, regionId, payPerMeter, tutorialPay).get + val task: NewTask = + if (MissionTypeTable.missionTypeIdToMissionType(mission.missionTypeId) == "auditOnboarding") + AuditTaskTable.getATutorialTask(mission.missionId) + else + AuditTaskTable.selectANewTask(streetEdgeId, mission.missionId) + val nextTempLabelId: Int = LabelTable.nextTempLabelId(userId) val missionSetProgress: MissionSetProgress = if (role == "Turker") MissionTable.getProgressOnMissionSet(user.username) else MissionTable.defaultAuditMissionSetProgress - // If there is a partially completed task in this mission, get that, o/w make a new one. - val task: Option[NewTask] = - if (MissionTypeTable.missionTypeIdToMissionType(mission.missionTypeId) == "auditOnboarding") - Some(AuditTaskTable.getATutorialTask(mission.missionId)) - else if (mission.currentAuditTaskId.isDefined) - AuditTaskTable.selectTaskFromTaskId(mission.currentAuditTaskId.get) - else - AuditTaskTable.selectANewTaskInARegion(regionId, user.userId, mission.missionId) - val nextTempLabelId: Int = LabelTable.nextTempLabelId(userId) - - // Check if they have already completed an audit mission. We send them to /validate after their first audit. + // Check if they have already completed an explore mission. We send them to /validate after their first audit // mission, but only after every third explore mission after that. val completedMission: Boolean = MissionTable.countCompletedMissions(user.userId, missionType = "audit") > 0 + // Overwrite the current_audit_task_id column to null if it has a value right now. It will be automatically + // updated to whatever an audit_task_id associated with the street edge they are about to start on. + if (mission.currentAuditTaskId.isDefined) { + MissionTable.updateAuditProgressOnly(userId, mission.missionId, mission.distanceProgress.get, None) + mission = MissionTable.resumeOrCreateNewAuditMission(userId, regionId, payPerMeter, tutorialPay).get + } + val lang: Lang = Configs.getLangFromRequest(request) val cityInfo: List[CityInfo] = Configs.getAllCityInfo(lang) val tutorialStreetId: Int = ConfigTable.getTutorialStreetId @@ -212,90 +306,24 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth if (missionSetProgress.missionType != "audit") { Future.successful(Redirect("/validate")) } else { - Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", task, mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops))) - } - case None => - Logger.error(s"Tried to explore region $regionId, but there is no neighborhood with that id.") - Future.successful(Redirect("/explore")) - } - - case None => - Future.successful(Redirect(s"/anonSignUp?url=/explore/region/$regionId")) - } - } - - /** - * Explore a given street. Optionally, a researcher can be placed at a specific lat/lng or panorama. - */ - def exploreStreet(streetEdgeId: Int, lat: Option[Double], lng: Option[Double], panoId: Option[String]) = UserAwareAction.async { implicit request => - val startAtPano: Boolean = panoId.isDefined - val startAtLatLng: Boolean = lat.isDefined && lng.isDefined - request.identity match { - case Some(user) => - val userId: UUID = user.userId - val regionOption: Option[Region] = StreetEdgeRegionTable.getNonDeletedRegionFromStreetId(streetEdgeId) - - if (regionOption.isEmpty) { - Logger.error(s"Either there is no region associated with street edge $streetEdgeId, or it is not a valid id.") - Future.successful(Redirect("/explore")) - } else { - val region: Region = regionOption.get - val regionId: Int = region.regionId - UserCurrentRegionTable.saveOrUpdate(userId, regionId) - - val role: String = user.role.getOrElse("") - val payPerMeter: Double = - if (role == "Turker") AMTAssignmentTable.TURKER_PAY_PER_METER else AMTAssignmentTable.VOLUNTEER_PAY - val tutorialPay: Double = - if (role == "Turker") AMTAssignmentTable.TURKER_TUTORIAL_PAY else AMTAssignmentTable.VOLUNTEER_PAY - var mission: Mission = - MissionTable.resumeOrCreateNewAuditMission(userId, regionId, payPerMeter, tutorialPay).get - val task: NewTask = - if (MissionTypeTable.missionTypeIdToMissionType(mission.missionTypeId) == "auditOnboarding") - AuditTaskTable.getATutorialTask(mission.missionId) - else - AuditTaskTable.selectANewTask(streetEdgeId, mission.missionId) - val nextTempLabelId: Int = LabelTable.nextTempLabelId(userId) - - val missionSetProgress: MissionSetProgress = - if (role == "Turker") MissionTable.getProgressOnMissionSet(user.username) - else MissionTable.defaultAuditMissionSetProgress - - // Check if they have already completed an explore mission. We send them to /validate after their first audit - // mission, but only after every third explore mission after that. - val completedMission: Boolean = MissionTable.countCompletedMissions(user.userId, missionType = "audit") > 0 - - // Overwrite the current_audit_task_id column to null if it has a value right now. It will be automatically - // updated to whatever an audit_task_id associated with the street edge they are about to start on. - if (mission.currentAuditTaskId.isDefined) { - MissionTable.updateAuditProgressOnly(userId, mission.missionId, mission.distanceProgress.get, None) - mission = MissionTable.resumeOrCreateNewAuditMission(userId, regionId, payPerMeter, tutorialPay).get - } - - val lang: Lang = Configs.getLangFromRequest(request) - val cityInfo: List[CityInfo] = Configs.getAllCityInfo(lang) - val tutorialStreetId: Int = ConfigTable.getTutorialStreetId - val makeCrops: Boolean = ConfigTable.getMakeCrops - if (missionSetProgress.missionType != "audit") { - Future.successful(Redirect("/validate")) - } else { - // If user is an admin and a panoId or lat/lng are supplied, send to that location, o/w send to street. - if (isAdmin(request.identity) && (startAtPano || startAtLatLng)) { - panoId match { - case Some(panoId) => Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops, None, None, Some(panoId)))) - case None => - (lat, lng) match { - case (Some(lat), Some(lng)) => Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops, Some(lat), Some(lng)))) - case (_, _) => Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, None, cityInfo, tutorialStreetId, makeCrops))) - } + // If user is an admin and a panoId or lat/lng are supplied, send to that location, o/w send to street. + if (isAdmin(request.identity) && (startAtPano || startAtLatLng)) { + panoId match { + case Some(panoId) => Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops, None, None, Some(panoId)))) + case None => + (lat, lng) match { + case (Some(lat), Some(lng)) => Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops, Some(lat), Some(lng)))) + case (_, _) => Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, None, cityInfo, tutorialStreetId, makeCrops))) + } + } + } else { + Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops))) } - } else { - Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops))) } } - } - case None => - Future.successful(Redirect(s"/anonSignUp?url=/explore/street/$streetEdgeId")) + case None => + Future.successful(Redirect(s"/anonSignUp?url=/explore/street/$streetEdgeId")) + } } } diff --git a/app/views/explore.scala.html b/app/views/explore.scala.html index a439251092..d61a440294 100644 --- a/app/views/explore.scala.html +++ b/app/views/explore.scala.html @@ -11,12 +11,6 @@ @currentCity = @{cityInfo.filter(c => c.current).head} @main(title, Some("/explore")) { - @if(userRoute.isDefined) { - @navbar(user, Some(s"/explore?routeId=${userRoute.get.routeId}&resumeRoute=true")) - } else { - @navbar(user, Some("/explore")) - } - diff --git a/app/views/exploreWrapper.scala.html b/app/views/exploreWrapper.scala.html new file mode 100644 index 0000000000..2ad09e81ff --- /dev/null +++ b/app/views/exploreWrapper.scala.html @@ -0,0 +1,11 @@ +@import models.user.User +@(title: String, user: Option[User] = None)(implicit lang: Lang) + +@main(title, Some("/explore")) { + @navbar(user, Some("/explore")) + + + + + +} \ No newline at end of file diff --git a/public/javascripts/SVLabel/css/svl.css b/public/javascripts/SVLabel/css/svl.css index 27f5cf2243..833a18dc88 100644 --- a/public/javascripts/SVLabel/css/svl.css +++ b/public/javascripts/SVLabel/css/svl.css @@ -209,3 +209,7 @@ input[type="radio"] { margin-top: 7px; height: 16px; } + +.test-server-banner { + display: none; +} \ No newline at end of file diff --git a/public/javascripts/SVLabel/src/SVLabel/Main.js b/public/javascripts/SVLabel/src/SVLabel/Main.js index 5be2ad1cf7..cbdb22796f 100644 --- a/public/javascripts/SVLabel/src/SVLabel/Main.js +++ b/public/javascripts/SVLabel/src/SVLabel/Main.js @@ -184,7 +184,7 @@ function Main (params) { google.maps.event.addDomListener(window, 'load', task.render); } - $("#navbar-retake-tutorial-btn").on('click', function () { + parent.$("#navbar-retake-tutorial-btn").on('click', function () { window.location.replace('/explore?retakeTutorial=true'); }); @@ -381,10 +381,11 @@ function Main (params) { // Use CSS zoom to scale the UI for users with high resolution screens. // Has only been tested on Chrome and Safari. Firefox doesn't support CSS zoom. - if (bowser.safari) { - svl.cssZoom = util.scaleUI(); - window.addEventListener('resize', (e) => { svl.cssZoom = util.scaleUI(); }); - } + // 9/8/2024 Update: This has been disabled in favor of an iframe-based scaling approach. + // if (bowser.safari) { + // svl.cssZoom = util.scaleUI(); + // window.addEventListener('resize', (e) => { svl.cssZoom = util.scaleUI(); }); + // } } } diff --git a/public/javascripts/SVLabel/src/SVLabel/onboarding/Onboarding.js b/public/javascripts/SVLabel/src/SVLabel/onboarding/Onboarding.js index fb41e579a7..f308798d04 100644 --- a/public/javascripts/SVLabel/src/SVLabel/onboarding/Onboarding.js +++ b/public/javascripts/SVLabel/src/SVLabel/onboarding/Onboarding.js @@ -46,7 +46,7 @@ function Onboarding(svl, audioEffect, compass, form, handAnimation, mapService, adjustMap(); - $("#navbar-retake-tutorial-btn").css("display", "none"); + parent.$("#navbar-retake-tutorial-btn").css("display", "none"); var canvasUI = uiOnboarding.canvas.get(0); if (canvasUI) ctx = canvasUI.getContext('2d'); diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js new file mode 100644 index 0000000000..b22470fb25 --- /dev/null +++ b/public/javascripts/common/wrapperFrame.js @@ -0,0 +1,22 @@ +const iframe = document.getElementById('wrapperFrame'); + +function scaleIframeContent() { + const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; + const contentElement = iframeDocument.querySelector('.tool-ui'); + + if (contentElement) { + const iframeWidth = window.innerWidth; + const contentWidth = contentElement.clientWidth; + + const scale = Math.min(1, iframeWidth / contentWidth); + + iframe.style.transform = `scale(${scale})`; + iframe.style.width = `${(1 / scale) * 100}vw`; + iframe.style.height = `calc(${(1 / scale) * 100}vh - ${(1 / scale) * 70}px)`; + } +} + +iframe.addEventListener('load', scaleIframeContent); +window.addEventListener('resize', scaleIframeContent); + +iframe.src = window.location.href; \ No newline at end of file diff --git a/public/stylesheets/wrapperFrame.css b/public/stylesheets/wrapperFrame.css new file mode 100644 index 0000000000..0a6a4ae787 --- /dev/null +++ b/public/stylesheets/wrapperFrame.css @@ -0,0 +1,14 @@ +html, body { + overflow: hidden; +} + +#wrapperFrame { + width: 100vw; + height: calc(100vh - 70px); + border: none; + transform-origin: 0 0; +} + +#mini-footer-audit { + display: none; +} \ No newline at end of file From 6d4e67e858223814c82edf5184116fd7580a3051 Mon Sep 17 00:00:00 2001 From: jsomeara Date: Fri, 13 Sep 2024 09:54:43 -0700 Subject: [PATCH 02/24] Apply height awareness for new beta auto resize. --- public/javascripts/common/wrapperFrame.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index b22470fb25..ddf2c15821 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -6,9 +6,11 @@ function scaleIframeContent() { if (contentElement) { const iframeWidth = window.innerWidth; + const iframeHeight = window.innerHeight - 70; const contentWidth = contentElement.clientWidth; + const contentHeight = iframeDocument.body.clientHeight + 90; - const scale = Math.min(1, iframeWidth / contentWidth); + const scale = Math.min(1, iframeWidth / contentWidth, iframeHeight / contentHeight); iframe.style.transform = `scale(${scale})`; iframe.style.width = `${(1 / scale) * 100}vw`; From edbf82f886298829b786272710213e75fb93b01d Mon Sep 17 00:00:00 2001 From: jsomeara Date: Fri, 13 Sep 2024 10:45:28 -0700 Subject: [PATCH 03/24] Fill up whole space, even when zoomed out --- app/views/explore.scala.html | 15 +++++++++++++++ public/javascripts/common/wrapperFrame.js | 4 ++-- public/stylesheets/wrapperFrame.css | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/views/explore.scala.html b/app/views/explore.scala.html index d61a440294..ee495f68ef 100644 --- a/app/views/explore.scala.html +++ b/app/views/explore.scala.html @@ -33,6 +33,21 @@ } + + @icons() @missionStartTutorial() diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index ddf2c15821..05f0349000 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -8,9 +8,9 @@ function scaleIframeContent() { const iframeWidth = window.innerWidth; const iframeHeight = window.innerHeight - 70; const contentWidth = contentElement.clientWidth; - const contentHeight = iframeDocument.body.clientHeight + 90; + const contentHeight = contentElement.clientHeight + 100; // Add 100px for padding purposes. - const scale = Math.min(1, iframeWidth / contentWidth, iframeHeight / contentHeight); + const scale = Math.min(iframeWidth / contentWidth, iframeHeight / contentHeight); iframe.style.transform = `scale(${scale})`; iframe.style.width = `${(1 / scale) * 100}vw`; diff --git a/public/stylesheets/wrapperFrame.css b/public/stylesheets/wrapperFrame.css index 0a6a4ae787..f60f73d084 100644 --- a/public/stylesheets/wrapperFrame.css +++ b/public/stylesheets/wrapperFrame.css @@ -1,5 +1,6 @@ html, body { overflow: hidden; + min-height: 100vh; } #wrapperFrame { From bc8aa6d70b4c2767c8acdc120dfa39894219c42f Mon Sep 17 00:00:00 2001 From: jsomeara Date: Fri, 13 Sep 2024 13:18:06 -0700 Subject: [PATCH 04/24] Mimick normal zoom on explorer iframe --- public/javascripts/common/wrapperFrame.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index 05f0349000..c315426c24 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -1,6 +1,7 @@ const iframe = document.getElementById('wrapperFrame'); function scaleIframeContent() { + iframe.contentWindow.devicePixelRatio = 1; const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; const contentElement = iframeDocument.querySelector('.tool-ui'); From d2b385833806a49404e4292c18b6044a3a77d64d Mon Sep 17 00:00:00 2001 From: jsomeara Date: Fri, 13 Sep 2024 19:41:23 -0700 Subject: [PATCH 05/24] auto resize: Passthrough iframe url changes to parent document --- public/javascripts/common/wrapperFrame.js | 32 +++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index c315426c24..4d85fab956 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -1,4 +1,22 @@ const iframe = document.getElementById('wrapperFrame'); +const testUserBanner = document.querySelector('.test-server-banner'); + +function iframeURLChange(callback) { + var unloadHandler = function () { + iframe.style.display = "none"; + setTimeout(() => { + callback(iframe.contentWindow.location.href); + }, 0) + }; + + function attachUnload() { + iframe.contentWindow.removeEventListener("unload", unloadHandler); + iframe.contentWindow.addEventListener("unload", unloadHandler); + } + + iframe.addEventListener("load", attachUnload); + attachUnload(); +} function scaleIframeContent() { iframe.contentWindow.devicePixelRatio = 1; @@ -7,7 +25,7 @@ function scaleIframeContent() { if (contentElement) { const iframeWidth = window.innerWidth; - const iframeHeight = window.innerHeight - 70; + const iframeHeight = window.innerHeight - 70 - (testUserBanner ? testUserBanner.clientHeight : 0); const contentWidth = contentElement.clientWidth; const contentHeight = contentElement.clientHeight + 100; // Add 100px for padding purposes. @@ -22,4 +40,14 @@ function scaleIframeContent() { iframe.addEventListener('load', scaleIframeContent); window.addEventListener('resize', scaleIframeContent); -iframe.src = window.location.href; \ No newline at end of file +iframe.src = window.location.href; +const firstLoadEventListener = iframe.addEventListener("load", function() { + iframeURLChange(function (newURL) { + window.location.href = newURL; + }); + iframe.removeEventListener("load", firstLoadEventListener) +}); + +setInterval(() => { + window.scrollTo(0, 0); +}, 100) \ No newline at end of file From 9486eb7b1b1a1bcddfcec6250fd05f036bef84f0 Mon Sep 17 00:00:00 2001 From: jsomeara Date: Sun, 22 Sep 2024 11:33:42 -0700 Subject: [PATCH 06/24] Passthrough svl and InitialMissionInstruction --- public/javascripts/common/wrapperFrame.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index 4d85fab956..a2765c7cd0 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -50,4 +50,6 @@ const firstLoadEventListener = iframe.addEventListener("load", function() { setInterval(() => { window.scrollTo(0, 0); + window.svl = iframe.contentWindow.svl; + window.InitialMissionInstruction = iframe.contentWindow.InitialMissionInstruction; }, 100) \ No newline at end of file From 708e8fcfd663358b7b5c4e7962905c05484c941c Mon Sep 17 00:00:00 2001 From: jsomeara Date: Sun, 22 Sep 2024 11:36:40 -0700 Subject: [PATCH 07/24] Remove Visit_Explore_Wrapper logging --- app/controllers/AuditController.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/AuditController.scala b/app/controllers/AuditController.scala index bd4f5e8cd9..f64b622c52 100644 --- a/app/controllers/AuditController.scala +++ b/app/controllers/AuditController.scala @@ -51,7 +51,6 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth request.identity match { case Some(user) => - WebpageActivityTable.save(WebpageActivity(0, user.userId.toString, ipAddress, "Visit_Explore_Wrapper", timestamp)) Future.successful(Ok(views.html.exploreWrapper("Project Sidewalk - Audit", Some(user)))) case None => Future.successful(Redirect("/anonSignUp?url=/explore")) From 15ebe37c99bd33946bcf41ea4b7c591a9f4777cd Mon Sep 17 00:00:00 2001 From: jsomeara Date: Sun, 22 Sep 2024 11:47:24 -0700 Subject: [PATCH 08/24] Move inline css to svl.css file --- app/views/explore.scala.html | 15 --------------- public/javascripts/SVLabel/css/svl.css | 13 +++++++++++++ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/app/views/explore.scala.html b/app/views/explore.scala.html index ee495f68ef..d61a440294 100644 --- a/app/views/explore.scala.html +++ b/app/views/explore.scala.html @@ -33,21 +33,6 @@ } - - @icons() @missionStartTutorial() diff --git a/public/javascripts/SVLabel/css/svl.css b/public/javascripts/SVLabel/css/svl.css index 833a18dc88..3e5a6b7ba2 100644 --- a/public/javascripts/SVLabel/css/svl.css +++ b/public/javascripts/SVLabel/css/svl.css @@ -1,3 +1,16 @@ +html, +body { + overflow: hidden; +} + +html { + padding-top: 0px; +} + +body { + padding-top: 30px; +} + text { visibility: hidden; } From a392b4b72f9d54d0f1d16e57009258475bd078be Mon Sep 17 00:00:00 2001 From: jsomeara Date: Sun, 22 Sep 2024 11:51:25 -0700 Subject: [PATCH 09/24] Switch element id from wrapperFrame to wrapper-frame --- app/views/exploreWrapper.scala.html | 2 +- public/javascripts/common/wrapperFrame.js | 2 +- public/stylesheets/wrapperFrame.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/exploreWrapper.scala.html b/app/views/exploreWrapper.scala.html index 2ad09e81ff..dbdf2faaa8 100644 --- a/app/views/exploreWrapper.scala.html +++ b/app/views/exploreWrapper.scala.html @@ -5,7 +5,7 @@ @navbar(user, Some("/explore")) - + } \ No newline at end of file diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index a2765c7cd0..7ae82d5fd9 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -1,4 +1,4 @@ -const iframe = document.getElementById('wrapperFrame'); +const iframe = document.getElementById('wrapper-frame'); const testUserBanner = document.querySelector('.test-server-banner'); function iframeURLChange(callback) { diff --git a/public/stylesheets/wrapperFrame.css b/public/stylesheets/wrapperFrame.css index f60f73d084..3c1461efc5 100644 --- a/public/stylesheets/wrapperFrame.css +++ b/public/stylesheets/wrapperFrame.css @@ -3,7 +3,7 @@ html, body { min-height: 100vh; } -#wrapperFrame { +#wrapper-frame { width: 100vw; height: calc(100vh - 70px); border: none; From beeda3f03134c50a491bfeb5b2b858820a5622bc Mon Sep 17 00:00:00 2001 From: jsomeara Date: Sun, 22 Sep 2024 12:17:20 -0700 Subject: [PATCH 10/24] Add bottom newlines to files --- app/views/exploreWrapper.scala.html | 2 +- public/javascripts/SVLabel/css/svl.css | 2 +- public/javascripts/common/wrapperFrame.js | 2 +- public/stylesheets/wrapperFrame.css | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/exploreWrapper.scala.html b/app/views/exploreWrapper.scala.html index dbdf2faaa8..e33f43e167 100644 --- a/app/views/exploreWrapper.scala.html +++ b/app/views/exploreWrapper.scala.html @@ -8,4 +8,4 @@ -} \ No newline at end of file +} diff --git a/public/javascripts/SVLabel/css/svl.css b/public/javascripts/SVLabel/css/svl.css index 3e5a6b7ba2..a05d777dc3 100644 --- a/public/javascripts/SVLabel/css/svl.css +++ b/public/javascripts/SVLabel/css/svl.css @@ -225,4 +225,4 @@ input[type="radio"] { .test-server-banner { display: none; -} \ No newline at end of file +} diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index 7ae82d5fd9..22e85c899b 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -52,4 +52,4 @@ setInterval(() => { window.scrollTo(0, 0); window.svl = iframe.contentWindow.svl; window.InitialMissionInstruction = iframe.contentWindow.InitialMissionInstruction; -}, 100) \ No newline at end of file +}, 100) diff --git a/public/stylesheets/wrapperFrame.css b/public/stylesheets/wrapperFrame.css index 3c1461efc5..7fe4c1e543 100644 --- a/public/stylesheets/wrapperFrame.css +++ b/public/stylesheets/wrapperFrame.css @@ -12,4 +12,4 @@ html, body { #mini-footer-audit { display: none; -} \ No newline at end of file +} From fbb991f6f546c0a73cebe1d43c7a617dc12d20a1 Mon Sep 17 00:00:00 2001 From: jsomeara Date: Sun, 22 Sep 2024 12:31:36 -0700 Subject: [PATCH 11/24] Add comments to wrapperFrame.js --- public/javascripts/common/wrapperFrame.js | 56 +++++++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index 22e85c899b..efd48e5dfe 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -1,55 +1,101 @@ const iframe = document.getElementById('wrapper-frame'); + +// Get the element for the dev enviroment warning. const testUserBanner = document.querySelector('.test-server-banner'); +// Define a function 'iframeURLChange' that takes a callback to execute when the iframe's URL changes. function iframeURLChange(callback) { + + // Define a function 'unloadHandler' that will be called when the iframe's content is unloaded. var unloadHandler = function () { + + // Hide the iframe by setting its display to "none". iframe.style.display = "none"; + + // Call the callback with the new iframe URL after a brief delay (0ms, to ensure execution timing). setTimeout(() => { callback(iframe.contentWindow.location.href); - }, 0) + }, 0); }; + // Define a function 'attachUnload' to add or replace the 'unload' event listener on the iframe's content window. function attachUnload() { + // First, remove any existing 'unload' event listener to prevent duplication. iframe.contentWindow.removeEventListener("unload", unloadHandler); + + // Add the 'unload' event listener to the iframe's content window to call 'unloadHandler' when the content unloads. iframe.contentWindow.addEventListener("unload", unloadHandler); } + // Attach the unload event when the iframe finishes loading. iframe.addEventListener("load", attachUnload); + + // Immediately call 'attachUnload' in case the content has already loaded. attachUnload(); } +// Define a function 'scaleIframeContent' to adjust the iframe content's scale to fit within the window. function scaleIframeContent() { + // Set the device pixel ratio to 1 to avoid pixelation issues with Google Street View. iframe.contentWindow.devicePixelRatio = 1; + + // Access the document inside the iframe. const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; + + // Find the element inside the iframe with the class 'tool-ui', which is the content to be scaled. const contentElement = iframeDocument.querySelector('.tool-ui'); + // Check if the content element exists before proceeding. if (contentElement) { + + // Get the width and height of the window (excluding 70px for fixed elements like navbar or the dev env warning). const iframeWidth = window.innerWidth; const iframeHeight = window.innerHeight - 70 - (testUserBanner ? testUserBanner.clientHeight : 0); + + // Get the width and height of the content inside the iframe. const contentWidth = contentElement.clientWidth; - const contentHeight = contentElement.clientHeight + 100; // Add 100px for padding purposes. + const contentHeight = contentElement.clientHeight + 100; // Add 100px for extra padding. + // Calculate the scale factor based on the smallest ratio between available window size and content size. const scale = Math.min(iframeWidth / contentWidth, iframeHeight / contentHeight); + // Apply the scaling transformation to the iframe based on the calculated scale factor. iframe.style.transform = `scale(${scale})`; + + // Adjust the iframe's width and height to scale the content correctly. iframe.style.width = `${(1 / scale) * 100}vw`; iframe.style.height = `calc(${(1 / scale) * 100}vh - ${(1 / scale) * 70}px)`; } } +// Attach the 'scaleIframeContent' function to the 'load' event on the iframe. iframe.addEventListener('load', scaleIframeContent); + +// Attach the 'scaleIframeContent' function to the 'resize' event on the window to resize the iframe when the window size changes. window.addEventListener('resize', scaleIframeContent); +// Set the source of the iframe to the current window's URL. iframe.src = window.location.href; + +// Add an event listener for the iframe's 'load' event, to detect when the iframe's content finishes loading. const firstLoadEventListener = iframe.addEventListener("load", function() { + + // Call the 'iframeURLChange' function to handle URL changes, and update the window location with the new URL. iframeURLChange(function (newURL) { window.location.href = newURL; }); - iframe.removeEventListener("load", firstLoadEventListener) + + // Remove this event listener after the first load event to prevent redundant calls. + iframe.removeEventListener("load", firstLoadEventListener); }); -setInterval(() => { +setInterval(() => { + // This line is needed to lock the scroll to the top in rare cases where it can get messed up. window.scrollTo(0, 0); + + // Passthrough the 'svl' variable from the iframe's window object into the main window object. window.svl = iframe.contentWindow.svl; + + // Passthrough the 'InitialMissionInstruction' variable from the iframe's window object into the main window object. window.InitialMissionInstruction = iframe.contentWindow.InitialMissionInstruction; -}, 100) +}, 100); From b2a7dc534474580d9b5f65cda3b2151e30f93634 Mon Sep 17 00:00:00 2001 From: jsomeara Date: Sun, 22 Sep 2024 15:38:52 -0700 Subject: [PATCH 12/24] Add to validate page --- app/controllers/ValidationController.scala | 41 ++++++++++++++++------ app/views/validation.scala.html | 2 -- app/views/validationWrapper.scala.html | 11 ++++++ public/javascripts/SVValidate/css/svv.css | 17 +++++++++ public/javascripts/SVValidate/src/Main.js | 9 ++--- 5 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 app/views/validationWrapper.scala.html diff --git a/app/controllers/ValidationController.scala b/app/controllers/ValidationController.scala index e1ad27108c..fb85d6b0b3 100644 --- a/app/controllers/ValidationController.scala +++ b/app/controllers/ValidationController.scala @@ -38,21 +38,40 @@ class ValidationController @Inject() (implicit val env: Environment[User, Sessio val validationMissionStr: String = "validation" /** - * Returns the validation page. + * Returns /validate wrapper page. */ - def validate = UserAwareAction.async { implicit request => + def validateWrapper = UserAwareAction.async { implicit request => + val timestamp: Timestamp = new Timestamp(Instant.now.toEpochMilli) + val ipAddress: String = request.remoteAddress + request.identity match { case Some(user) => - val adminParams = AdminValidateParams(adminVersion = false) - val r: UserAwareRequest[AnyContent] = request - val validationData = getDataForValidationPages(request, labelCount = 10, "Visit_Validate", adminParams) - if (validationData._4.missionType != "validation") { - Future.successful(Redirect("/explore")) - } else { - Future.successful(Ok(views.html.validation("Sidewalk - Validate", Some(user), adminParams, validationData._1, validationData._2, validationData._3, validationData._4.numComplete, validationData._5, validationData._6, validationData._7))) - } + Future.successful(Ok(views.html.validationWrapper("Sidewalk - Validate", Some(user)))) case None => - Future.successful(Redirect(s"/anonSignUp?url=/validate")); + Future.successful(Redirect("/anonSignUp?url=/validate")) + } + } + + /** + * Returns the validation page. + */ + def validate = UserAwareAction.async { implicit request => + if (request.headers.get("Sec-Fetch-Dest").getOrElse("") == "document") { + validateWrapper.apply(request) + } else { + request.identity match { + case Some(user) => + val adminParams = AdminValidateParams(adminVersion = false) + val r: UserAwareRequest[AnyContent] = request + val validationData = getDataForValidationPages(request, labelCount = 10, "Visit_Validate", adminParams) + if (validationData._4.missionType != "validation") { + Future.successful(Redirect("/explore")) + } else { + Future.successful(Ok(views.html.validation("Sidewalk - Validate", Some(user), adminParams, validationData._1, validationData._2, validationData._3, validationData._4.numComplete, validationData._5, validationData._6, validationData._7))) + } + case None => + Future.successful(Redirect(s"/anonSignUp?url=/validate")); + } } } diff --git a/app/views/validation.scala.html b/app/views/validation.scala.html index e6582466b1..456eed5d98 100644 --- a/app/views/validation.scala.html +++ b/app/views/validation.scala.html @@ -8,8 +8,6 @@ @currentCity = @{cityInfo.filter(c => c.current).head} @main(title, Some("/validate")) { - @navbar(user, Some("/validate")) - @icons() diff --git a/app/views/validationWrapper.scala.html b/app/views/validationWrapper.scala.html new file mode 100644 index 0000000000..c0e3da8e76 --- /dev/null +++ b/app/views/validationWrapper.scala.html @@ -0,0 +1,11 @@ +@import models.user.User +@(title: String, user: Option[User] = None)(implicit lang: Lang) + +@main(title, Some("/validate")) { + @navbar(user, Some("/validate")) + + + + + +} diff --git a/public/javascripts/SVValidate/css/svv.css b/public/javascripts/SVValidate/css/svv.css index 56941e0497..cd22db34d8 100644 --- a/public/javascripts/SVValidate/css/svv.css +++ b/public/javascripts/SVValidate/css/svv.css @@ -1,3 +1,16 @@ +html, +body { + overflow: hidden; +} + +html { + padding-top: 0px; +} + +body { + padding-top: 30px; +} + #svv-application-holder { background: rgba(255,255,255,1); margin: 0 auto; @@ -82,3 +95,7 @@ input[type="radio"] { cursor: default; opacity: 0.5; } + +.test-server-banner { + display: none; +} diff --git a/public/javascripts/SVValidate/src/Main.js b/public/javascripts/SVValidate/src/Main.js index 3e4667eb79..26a7b07533 100644 --- a/public/javascripts/SVValidate/src/Main.js +++ b/public/javascripts/SVValidate/src/Main.js @@ -246,10 +246,11 @@ function Main (param) { // Use CSS zoom to scale the UI for users with high resolution screens. // Has only been tested on Chrome and Safari. Firefox doesn't support CSS zoom. - if (!isMobile() && bowser.safari) { - svv.cssZoom = util.scaleUI(); - window.addEventListener('resize', (e) => { svv.cssZoom = util.scaleUI(); }); - } + // 9/8/2024 Update: This has been disabled in favor of an iframe-based scaling approach. + // if (!isMobile() && bowser.safari) { + // svv.cssZoom = util.scaleUI(); + // window.addEventListener('resize', (e) => { svv.cssZoom = util.scaleUI(); }); + // } } // Gets all the text on the validation page for the correct language. From 5b8db71d12d36a88ff7e89ae6566f243a438bd4f Mon Sep 17 00:00:00 2001 From: jsomeara Date: Sun, 22 Sep 2024 15:54:43 -0700 Subject: [PATCH 13/24] add auto resize to new validate beta --- app/controllers/ValidationController.scala | 43 +++++++++++++++------ app/views/newValidateBeta.scala.html | 2 - app/views/newValidateBetaWrapper.scala.html | 11 ++++++ 3 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 app/views/newValidateBetaWrapper.scala.html diff --git a/app/controllers/ValidationController.scala b/app/controllers/ValidationController.scala index fb85d6b0b3..a7c147eda5 100644 --- a/app/controllers/ValidationController.scala +++ b/app/controllers/ValidationController.scala @@ -75,23 +75,42 @@ class ValidationController @Inject() (implicit val env: Environment[User, Sessio } } + /** + * Returns /newValidateBeta wrapper page. + */ + def newValidateBetaWrapper = UserAwareAction.async { implicit request => + val timestamp: Timestamp = new Timestamp(Instant.now.toEpochMilli) + val ipAddress: String = request.remoteAddress + + request.identity match { + case Some(user) => + Future.successful(Ok(views.html.newValidateBetaWrapper("Sidewalk - NewValidateBeta", Some(user)))) + case None => + Future.successful(Redirect("/anonSignUp?url=/newValidateBeta")) + } + } + /** * Returns the new validation that includes severity and tags page. */ def newValidateBeta = UserAwareAction.async { implicit request => if (isAdmin(request.identity)) { - request.identity match { - case Some(user) => - val adminParams = AdminValidateParams(adminVersion = false) - val validationData = getDataForValidationPages(request, labelCount = 10, "Visit_NewValidateBeta", adminParams) - if (validationData._4.missionType != "validation") { - Future.successful(Redirect("/explore")) - } else { - val tags: List[Tag] = TagTable.getTagsForCurrentCity - Future.successful(Ok(views.html.newValidateBeta("Sidewalk - NewValidateBeta", Some(user), adminParams, validationData._1, validationData._2, validationData._3, validationData._4.numComplete, validationData._5, validationData._6, tags))) - } - case None => - Future.successful(Redirect(s"/anonSignUp?url=/newValidateBeta")); + if (request.headers.get("Sec-Fetch-Dest").getOrElse("") == "document") { + newValidateBetaWrapper.apply(request) + } else { + request.identity match { + case Some(user) => + val adminParams = AdminValidateParams(adminVersion = false) + val validationData = getDataForValidationPages(request, labelCount = 10, "Visit_NewValidateBeta", adminParams) + if (validationData._4.missionType != "validation") { + Future.successful(Redirect("/explore")) + } else { + val tags: List[Tag] = TagTable.getTagsForCurrentCity + Future.successful(Ok(views.html.newValidateBeta("Sidewalk - NewValidateBeta", Some(user), adminParams, validationData._1, validationData._2, validationData._3, validationData._4.numComplete, validationData._5, validationData._6, tags))) + } + case None => + Future.successful(Redirect(s"/anonSignUp?url=/newValidateBeta")); + } } } else { Future.failed(new AuthenticationException("This is a beta currently only open to Admins.")) diff --git a/app/views/newValidateBeta.scala.html b/app/views/newValidateBeta.scala.html index 185834cd0a..bbaccdc731 100644 --- a/app/views/newValidateBeta.scala.html +++ b/app/views/newValidateBeta.scala.html @@ -9,8 +9,6 @@ @(title: String, user: Option[User] = None, adminParams: AdminValidateParams, mission: Option[JsValue], labelList: Option[JsValue], progress: Option[JsValue], missionSetProgress: Int, hasNextMission: Boolean, completedValidations: Int, tagList: List[Tag])(implicit lang: Lang) @main(title, Some("/newValidateBeta")) { - @navbar(user, Some("/newValidateBeta")) - @icons() diff --git a/app/views/newValidateBetaWrapper.scala.html b/app/views/newValidateBetaWrapper.scala.html new file mode 100644 index 0000000000..75938d963f --- /dev/null +++ b/app/views/newValidateBetaWrapper.scala.html @@ -0,0 +1,11 @@ +@import models.user.User +@(title: String, user: Option[User] = None)(implicit lang: Lang) + +@main(title, Some("/validate")) { + @navbar(user, Some("/newValidateBeta")) + + + + + +} From 782d6a277bc0b6d5c4ddba85a23532e8dd9e0dd6 Mon Sep 17 00:00:00 2001 From: Mikey Saugstad Date: Mon, 30 Sep 2024 14:44:11 -0700 Subject: [PATCH 14/24] reduces bottom padding a bit for auto zoom --- public/javascripts/common/wrapperFrame.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index efd48e5dfe..2c77185b0b 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -54,7 +54,7 @@ function scaleIframeContent() { // Get the width and height of the content inside the iframe. const contentWidth = contentElement.clientWidth; - const contentHeight = contentElement.clientHeight + 100; // Add 100px for extra padding. + const contentHeight = contentElement.clientHeight + 60; // Add 100px for extra padding. // Calculate the scale factor based on the smallest ratio between available window size and content size. const scale = Math.min(iframeWidth / contentWidth, iframeHeight / contentHeight); From 0fedac68c7745bb6f6651334b90cab66e56e6702 Mon Sep 17 00:00:00 2001 From: John O'Meara Date: Sat, 23 Nov 2024 01:40:15 +0000 Subject: [PATCH 15/24] Added ability to specify percentage of vertical whitespace --- public/javascripts/SVLabel/css/svl.css | 2 +- public/javascripts/common/wrapperFrame.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/public/javascripts/SVLabel/css/svl.css b/public/javascripts/SVLabel/css/svl.css index a05d777dc3..8e5b66da52 100644 --- a/public/javascripts/SVLabel/css/svl.css +++ b/public/javascripts/SVLabel/css/svl.css @@ -8,7 +8,7 @@ html { } body { - padding-top: 30px; + padding-top: 0px; } text { diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index 2c77185b0b..e04a978c44 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -1,3 +1,5 @@ +const PERCENT_VERTICAL_PADDING = 2 + const iframe = document.getElementById('wrapper-frame'); // Get the element for the dev enviroment warning. @@ -54,7 +56,8 @@ function scaleIframeContent() { // Get the width and height of the content inside the iframe. const contentWidth = contentElement.clientWidth; - const contentHeight = contentElement.clientHeight + 60; // Add 100px for extra padding. + const contentHeight = contentElement.clientHeight + iframeHeight * (PERCENT_VERTICAL_PADDING / 100) * 2; + iframe.style.paddingTop = iframeHeight * (PERCENT_VERTICAL_PADDING / 100) + "px" // Calculate the scale factor based on the smallest ratio between available window size and content size. const scale = Math.min(iframeWidth / contentWidth, iframeHeight / contentHeight); From 56c56bc3ad76349f3ac7929884b36c0e08781d71 Mon Sep 17 00:00:00 2001 From: John O'Meara Date: Sat, 23 Nov 2024 02:26:22 +0000 Subject: [PATCH 16/24] Make SVL padding top 0 --- public/javascripts/SVValidate/css/svv.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/javascripts/SVValidate/css/svv.css b/public/javascripts/SVValidate/css/svv.css index cd22db34d8..374f371a4f 100644 --- a/public/javascripts/SVValidate/css/svv.css +++ b/public/javascripts/SVValidate/css/svv.css @@ -8,7 +8,7 @@ html { } body { - padding-top: 30px; + padding-top: 0px; } #svv-application-holder { From 107560de4ffbbfabf1572dd87e8e2117aa9ca768 Mon Sep 17 00:00:00 2001 From: Mikey Saugstad Date: Mon, 25 Nov 2024 17:38:05 -0700 Subject: [PATCH 17/24] small code cleanup for automated zoom with iframes --- app/controllers/AdminController.scala | 6 ++-- app/controllers/AttributeController.scala | 2 +- app/controllers/AuditController.scala | 36 ++++++++++------------ app/controllers/ValidationController.scala | 7 ----- app/models/label/LabelTable.scala | 2 +- app/views/forgotPassword.scala.html | 2 +- app/views/resetPassword.scala.html | 2 +- app/views/signIn.scala.html | 2 +- app/views/signInMobile.scala.html | 2 +- app/views/signUp.scala.html | 2 +- app/views/signUpMobile.scala.html | 2 +- public/javascripts/SVLabel/css/svl.css | 1 + public/javascripts/SVValidate/css/svv.css | 1 + public/javascripts/common/wrapperFrame.js | 17 +++++----- 14 files changed, 38 insertions(+), 46 deletions(-) diff --git a/app/controllers/AdminController.scala b/app/controllers/AdminController.scala index 1acc03dd09..afc8a9cddd 100644 --- a/app/controllers/AdminController.scala +++ b/app/controllers/AdminController.scala @@ -57,7 +57,7 @@ class AdminController @Inject() (implicit val env: Environment[User, SessionAuth val user: User = request.identity.get WebpageActivityTable.save(WebpageActivity(0, user.userId.toString, ipAddress, "Visit_Admin", timestamp)) } - Future.successful(Ok(views.html.admin.index("Project Sidewalk", request.identity))) + Future.successful(Ok(views.html.admin.index("Sidewalk - Admin", request.identity))) } else { Future.failed(new AuthenticationException("User is not an administrator")) } @@ -76,7 +76,7 @@ class AdminController @Inject() (implicit val env: Environment[User, SessionAuth if (Messages("measurement.system") == "metric") AuditTaskTable.getDistanceAudited(userId) / 1000F else AuditTaskTable.getDistanceAudited(userId) * METERS_TO_MILES } - Future.successful(Ok(views.html.admin.user("Project Sidewalk", request.identity.get, user, auditedDistance))) + Future.successful(Ok(views.html.admin.user("Sidewalk - AdminUser", request.identity.get, user, auditedDistance))) case _ => Future.failed(new NotFoundException("Username not found.")) } } else { @@ -98,7 +98,7 @@ class AdminController @Inject() (implicit val env: Environment[User, SessionAuth def task(taskId: Int) = UserAwareAction.async { implicit request => if (isAdmin(request.identity)) { AuditTaskTable.find(taskId) match { - case Some(task) => Future.successful(Ok(views.html.admin.task("Project Sidewalk", request.identity, task))) + case Some(task) => Future.successful(Ok(views.html.admin.task("Sidewalk - AdminTask", request.identity, task))) case _ => Future.successful(Redirect("/")) } } else { diff --git a/app/controllers/AttributeController.scala b/app/controllers/AttributeController.scala index bc7854bfd2..d43f6e2fb4 100644 --- a/app/controllers/AttributeController.scala +++ b/app/controllers/AttributeController.scala @@ -32,7 +32,7 @@ class AttributeController @Inject() (implicit val env: Environment[User, Session */ def index = UserAwareAction.async { implicit request => if (isAdmin(request.identity)) { - Future.successful(Ok(views.html.clustering("Project Sidewalk", request.identity))) + Future.successful(Ok(views.html.clustering("Sidewalk - Clustering", request.identity))) } else { Future.successful(Redirect("/")) } diff --git a/app/controllers/AuditController.scala b/app/controllers/AuditController.scala index f64b622c52..480f8ff6de 100644 --- a/app/controllers/AuditController.scala +++ b/app/controllers/AuditController.scala @@ -21,8 +21,7 @@ import models.street.StreetEdgeRegionTable import models.user._ import models.utils.{CityInfo, Configs} import play.api.libs.json._ -import play.api.{Logger, Play} -import play.api.Play.current +import play.api.Logger import play.api.i18n.Lang import play.api.mvc._ import scala.concurrent.Future @@ -46,12 +45,9 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth * Returns /explore wrapper page. */ def exploreWrapper = UserAwareAction.async { implicit request => - val timestamp: Timestamp = new Timestamp(Instant.now.toEpochMilli) - val ipAddress: String = request.remoteAddress - request.identity match { case Some(user) => - Future.successful(Ok(views.html.exploreWrapper("Project Sidewalk - Audit", Some(user)))) + Future.successful(Ok(views.html.exploreWrapper("Sidewalk - Explore", Some(user)))) case None => Future.successful(Redirect("/anonSignUp?url=/explore")) } @@ -75,14 +71,14 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth request.identity match { case Some(user) => - // If the user is a Turker and has audited less than 50 meters in the current region, then delete the current region. + // If the user is a Turker and has audited <50 meters in the current region, then delete the current region. val currentMeters: Option[Float] = MissionTable.getMetersAuditedInCurrentMission(user.userId) if (user.role.getOrElse("") == "Turker" && currentMeters.isDefined && currentMeters.get < 50) { UserCurrentRegionTable.delete(user.userId) } - // Check if user has an active route or create a new one if routeId was supplied. If resumeRoute is false and no - // routeId was supplied, then the function should return None and the user is not sent on a specific route. + // Check if user has an active route or create a new one if routeId was supplied. If resumeRoute is false and + // no routeId was supplied, then the function should return None and the user is not sent on a specific route. val userRoute: Option[UserRoute] = UserRouteTable.setUpPossibleUserRoute(routeId, user.userId, resumeRoute) val route: Option[Route] = userRoute.flatMap(ur => RouteTable.getRoute(ur.routeId)) @@ -152,8 +148,8 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth mission = MissionTable.getMission(mission.missionId).get } - // Check if they have already completed an explore mission. We send them to /validate after their first explore - // mission, but only after every third explore mission after that. + // Check if they have already completed an explore mission. We send them to /validate after their first + // explore mission, but only after every third explore mission after that. val completedMissions: Boolean = MissionTable.countCompletedMissions(user.userId, missionType = "audit") > 0 val tutorialStreetId: Int = ConfigTable.getTutorialStreetId @@ -165,7 +161,7 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth Future.successful(Redirect("/validate")) } else { // On the crowdstudy server, we want to assign users to a study group. - val response = Ok(views.html.explore("Project Sidewalk - Audit", task, mission, region.get, userRoute, missionSetProgress.numComplete, completedMissions, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops)) + val response = Ok(views.html.explore("Sidewalk - Explore", task, mission, region.get, userRoute, missionSetProgress.numComplete, completedMissions, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops)) if (cityId == "crowdstudy" && studyGroup.nonEmpty) Future.successful(response.withCookies(Cookie("SIDEWALK_STUDY_GROUP", studyGroup, httpOnly = false))) else Future.successful(response) } @@ -223,7 +219,7 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth AuditTaskTable.selectANewTaskInARegion(regionId, user.userId, mission.missionId) val nextTempLabelId: Int = LabelTable.nextTempLabelId(userId) - // Check if they have already completed an audit mission. We send them to /validate after their first audit. + // Check if they've already completed an audit mission. We send them to /validate after their first audit. // mission, but only after every third explore mission after that. val completedMission: Boolean = MissionTable.countCompletedMissions(user.userId, missionType = "audit") > 0 @@ -234,7 +230,7 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth if (missionSetProgress.missionType != "audit") { Future.successful(Redirect("/validate")) } else { - Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", task, mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops))) + Future.successful(Ok(views.html.explore("Sidewalk - Explore", task, mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops))) } case None => Logger.error(s"Tried to explore region $regionId, but there is no neighborhood with that id.") @@ -287,8 +283,8 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth if (role == "Turker") MissionTable.getProgressOnMissionSet(user.username) else MissionTable.defaultAuditMissionSetProgress - // Check if they have already completed an explore mission. We send them to /validate after their first audit - // mission, but only after every third explore mission after that. + // Check if they have already completed an explore mission. We send them to /validate after their first + // audit mission, but only after every third explore mission after that. val completedMission: Boolean = MissionTable.countCompletedMissions(user.userId, missionType = "audit") > 0 // Overwrite the current_audit_task_id column to null if it has a value right now. It will be automatically @@ -308,15 +304,15 @@ class AuditController @Inject() (implicit val env: Environment[User, SessionAuth // If user is an admin and a panoId or lat/lng are supplied, send to that location, o/w send to street. if (isAdmin(request.identity) && (startAtPano || startAtLatLng)) { panoId match { - case Some(panoId) => Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops, None, None, Some(panoId)))) + case Some(panoId) => Future.successful(Ok(views.html.explore("Sidewalk - Explore", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops, None, None, Some(panoId)))) case None => (lat, lng) match { - case (Some(lat), Some(lng)) => Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops, Some(lat), Some(lng)))) - case (_, _) => Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, None, cityInfo, tutorialStreetId, makeCrops))) + case (Some(lat), Some(lng)) => Future.successful(Ok(views.html.explore("Sidewalk - Explore", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops, Some(lat), Some(lng)))) + case (_, _) => Future.successful(Ok(views.html.explore("Sidewalk - Explore", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, None, cityInfo, tutorialStreetId, makeCrops))) } } } else { - Future.successful(Ok(views.html.explore("Project Sidewalk - Audit", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops))) + Future.successful(Ok(views.html.explore("Sidewalk - Explore", Some(task), mission, region, None, missionSetProgress.numComplete, completedMission, nextTempLabelId, Some(user), cityInfo, tutorialStreetId, makeCrops))) } } } diff --git a/app/controllers/ValidationController.scala b/app/controllers/ValidationController.scala index a7c147eda5..808e775af9 100644 --- a/app/controllers/ValidationController.scala +++ b/app/controllers/ValidationController.scala @@ -41,9 +41,6 @@ class ValidationController @Inject() (implicit val env: Environment[User, Sessio * Returns /validate wrapper page. */ def validateWrapper = UserAwareAction.async { implicit request => - val timestamp: Timestamp = new Timestamp(Instant.now.toEpochMilli) - val ipAddress: String = request.remoteAddress - request.identity match { case Some(user) => Future.successful(Ok(views.html.validationWrapper("Sidewalk - Validate", Some(user)))) @@ -62,7 +59,6 @@ class ValidationController @Inject() (implicit val env: Environment[User, Sessio request.identity match { case Some(user) => val adminParams = AdminValidateParams(adminVersion = false) - val r: UserAwareRequest[AnyContent] = request val validationData = getDataForValidationPages(request, labelCount = 10, "Visit_Validate", adminParams) if (validationData._4.missionType != "validation") { Future.successful(Redirect("/explore")) @@ -79,9 +75,6 @@ class ValidationController @Inject() (implicit val env: Environment[User, Sessio * Returns /newValidateBeta wrapper page. */ def newValidateBetaWrapper = UserAwareAction.async { implicit request => - val timestamp: Timestamp = new Timestamp(Instant.now.toEpochMilli) - val ipAddress: String = request.remoteAddress - request.identity match { case Some(user) => Future.successful(Ok(views.html.newValidateBetaWrapper("Sidewalk - NewValidateBeta", Some(user)))) diff --git a/app/models/label/LabelTable.scala b/app/models/label/LabelTable.scala index 4622d7e779..86f2326612 100644 --- a/app/models/label/LabelTable.scala +++ b/app/models/label/LabelTable.scala @@ -357,7 +357,7 @@ object LabelTable { |INNER JOIN label_type ON label.label_type_id = label_type.label_type_id |WHERE (time_created AT TIME ZONE 'US/Pacific') > (now() AT TIME ZONE 'US/Pacific') - interval '168 hours' | AND label.deleted = false - | AND label_type.label_type = $labelType;""".stripMargin + | AND label_type.label_type = '$labelType';""".stripMargin ) countQuery.first } diff --git a/app/views/forgotPassword.scala.html b/app/views/forgotPassword.scala.html index 0a58dd45cc..62b63b10b5 100644 --- a/app/views/forgotPassword.scala.html +++ b/app/views/forgotPassword.scala.html @@ -1,7 +1,7 @@ @(forgotPasswordForm: Form[String])(implicit request: RequestHeader, lang: Lang) @import views.html.bootstrap._ -@main("Project Sidewalk - Recover Password") { +@main("Sidewalk - Recover Password") { @navbar(None) @request.flash.get("error").map { msg =>
× diff --git a/app/views/signUp.scala.html b/app/views/signUp.scala.html index 1a0c1d7e3d..e71edf616d 100644 --- a/app/views/signUp.scala.html +++ b/app/views/signUp.scala.html @@ -1,7 +1,7 @@ @(signInForm: Form[forms.SignUpForm.Data], url: String = "/")(implicit request: RequestHeader, lang: Lang) @import views.html.bootstrap._ -@main("Project Sidewalk - Sign Up") { +@main("Sidewalk - Sign Up") { @navbar(None, Some("/signUp")) @request.flash.get("error").map { msg =>
diff --git a/public/javascripts/SVLabel/css/svl.css b/public/javascripts/SVLabel/css/svl.css index 8e5b66da52..751ca46a26 100644 --- a/public/javascripts/SVLabel/css/svl.css +++ b/public/javascripts/SVLabel/css/svl.css @@ -223,6 +223,7 @@ input[type="radio"] { height: 16px; } +/* Hides the duplicate test server banner that appears within the iframe. */ .test-server-banner { display: none; } diff --git a/public/javascripts/SVValidate/css/svv.css b/public/javascripts/SVValidate/css/svv.css index 374f371a4f..262e63a26f 100644 --- a/public/javascripts/SVValidate/css/svv.css +++ b/public/javascripts/SVValidate/css/svv.css @@ -96,6 +96,7 @@ input[type="radio"] { opacity: 0.5; } +/* Hides the duplicate test server banner that appears within the iframe. */ .test-server-banner { display: none; } diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index e04a978c44..d95450d956 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -2,8 +2,8 @@ const PERCENT_VERTICAL_PADDING = 2 const iframe = document.getElementById('wrapper-frame'); -// Get the element for the dev enviroment warning. -const testUserBanner = document.querySelector('.test-server-banner'); +// Get the element for the dev environment warning. +const testServerBanner = document.querySelector('.test-server-banner'); // Define a function 'iframeURLChange' that takes a callback to execute when the iframe's URL changes. function iframeURLChange(callback) { @@ -25,7 +25,7 @@ function iframeURLChange(callback) { // First, remove any existing 'unload' event listener to prevent duplication. iframe.contentWindow.removeEventListener("unload", unloadHandler); - // Add the 'unload' event listener to the iframe's content window to call 'unloadHandler' when the content unloads. + // Add 'unload' event listener to the iframe's content window to call 'unloadHandler' when the content unloads. iframe.contentWindow.addEventListener("unload", unloadHandler); } @@ -50,11 +50,11 @@ function scaleIframeContent() { // Check if the content element exists before proceeding. if (contentElement) { - // Get the width and height of the window (excluding 70px for fixed elements like navbar or the dev env warning). + // Get the width & height of the window (excluding 70px for fixed elements like navbar or the dev env warning). const iframeWidth = window.innerWidth; - const iframeHeight = window.innerHeight - 70 - (testUserBanner ? testUserBanner.clientHeight : 0); + const iframeHeight = window.innerHeight - 70 - (testServerBanner ? testServerBanner.clientHeight : 0); - // Get the width and height of the content inside the iframe. + // Get the width & height of the content inside the iframe. const contentWidth = contentElement.clientWidth; const contentHeight = contentElement.clientHeight + iframeHeight * (PERCENT_VERTICAL_PADDING / 100) * 2; iframe.style.paddingTop = iframeHeight * (PERCENT_VERTICAL_PADDING / 100) + "px" @@ -96,8 +96,9 @@ setInterval(() => { // This line is needed to lock the scroll to the top in rare cases where it can get messed up. window.scrollTo(0, 0); - // Passthrough the 'svl' variable from the iframe's window object into the main window object. - window.svl = iframe.contentWindow.svl; + // Passthrough the 'svl'/'svv' variables from the iframe's window object into the main window object. + if (iframe.contentWindow.svl) window.svl = iframe.contentWindow.svl; + if (iframe.contentWindow.svv) window.svv = iframe.contentWindow.svv; // Passthrough the 'InitialMissionInstruction' variable from the iframe's window object into the main window object. window.InitialMissionInstruction = iframe.contentWindow.InitialMissionInstruction; From fd77ca1612e76629000f38d3adfba29ca941bcbb Mon Sep 17 00:00:00 2001 From: John O'Meara Date: Tue, 26 Nov 2024 02:39:34 +0000 Subject: [PATCH 18/24] fix inconsistent padding + minor fixes --- .../javascripts/SVLabel/src/SVLabel/Main.js | 8 ----- public/javascripts/SVValidate/src/Main.js | 8 ----- public/javascripts/common/Utilities.js | 32 ------------------- public/javascripts/common/wrapperFrame.js | 14 ++++++-- public/stylesheets/main.css | 16 ---------- public/stylesheets/wrapperFrame.css | 4 --- 6 files changed, 11 insertions(+), 71 deletions(-) diff --git a/public/javascripts/SVLabel/src/SVLabel/Main.js b/public/javascripts/SVLabel/src/SVLabel/Main.js index 3abe0e0f8b..b1e7e4ad2c 100644 --- a/public/javascripts/SVLabel/src/SVLabel/Main.js +++ b/public/javascripts/SVLabel/src/SVLabel/Main.js @@ -381,14 +381,6 @@ function Main (params) { startTheMission(mission, currentNeighborhood); } - - // Use CSS zoom to scale the UI for users with high resolution screens. - // Has only been tested on Chrome and Safari. Firefox doesn't support CSS zoom. - // 9/8/2024 Update: This has been disabled in favor of an iframe-based scaling approach. - // if (bowser.safari) { - // svl.cssZoom = util.scaleUI(); - // window.addEventListener('resize', (e) => { svl.cssZoom = util.scaleUI(); }); - // } } } diff --git a/public/javascripts/SVValidate/src/Main.js b/public/javascripts/SVValidate/src/Main.js index 872bd8b686..5e81a97ce6 100644 --- a/public/javascripts/SVValidate/src/Main.js +++ b/public/javascripts/SVValidate/src/Main.js @@ -244,14 +244,6 @@ function Main (param) { const labelType = param.labelList[0].getAuditProperty('labelType'); const missionStartTutorial = new MissionStartTutorial('validate', labelType, { nLabels: param.mission.labels_validated }, svv, param.language); - - // Use CSS zoom to scale the UI for users with high resolution screens. - // Has only been tested on Chrome and Safari. Firefox doesn't support CSS zoom. - // 9/8/2024 Update: This has been disabled in favor of an iframe-based scaling approach. - // if (!isMobile() && bowser.safari) { - // svv.cssZoom = util.scaleUI(); - // window.addEventListener('resize', (e) => { svv.cssZoom = util.scaleUI(); }); - // } } // Gets all the text on the validation page for the correct language. diff --git a/public/javascripts/common/Utilities.js b/public/javascripts/common/Utilities.js index 7849f42c6c..b0d7a86063 100644 --- a/public/javascripts/common/Utilities.js +++ b/public/javascripts/common/Utilities.js @@ -162,38 +162,6 @@ function camelToKebab(theString) { } util.camelToKebab = camelToKebab; -/** - * Scales the UI on the Explore or Validate pages using CSS zoom. This is necessary because the UI is not responsive. - * - * This should only be called from the Explore or Validate pages at this time. We can always make this function more - * generic in the future. - * @returns {number} - */ -function scaleUI() { - var toolCSSZoom = 100; - if (!bowser.safari) return toolCSSZoom; // Only tested for Chrome/Safari so far. - - var toolUI = document.querySelector('.tool-ui'); - var mst = document.querySelector('.mst-content'); - var zoomPercent = 50; - - // Start with the tool-ui at 50% zoom and find the maximum zoom level that is still visible. - if (!!toolUI.offsetParent) { - zoomPercent = _findMaxZoomLevel(toolUI, zoomPercent); - toolCSSZoom = zoomPercent; - } - - // If the Mission Start Tutorial is visible, scale it as well. - if (!!mst.offsetParent) { - document.querySelector('.mission-start-tutorial-overlay').style.height = 'calc(100% - 70px)'; - if (zoomPercent > 50) zoomPercent -= 20; // Should be similar as tool-ui, don't need to start at 50%. - zoomPercent = _findMaxZoomLevel(mst, zoomPercent); - } - - return toolCSSZoom; -} -util.scaleUI = scaleUI; - // Returns true if the element is fully visible, false otherwise. Takes into account CSS zoom (tested on chrome/safari). function _isVisible(elem) { var zoomFactor = parseFloat(elem.style.zoom) / 100.0 || 1; diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index d95450d956..626ffabd05 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -1,4 +1,11 @@ -const PERCENT_VERTICAL_PADDING = 2 +const PERCENT_VERTICAL_PADDING = 5 + +// These offsets are in terms of iframe context pixels, not the current page pixels. +const PADDING_OFFSETS_PX = { + "/explore": 13, + "/validate": 28, + "/newValidateBeta": 35 +} const iframe = document.getElementById('wrapper-frame'); @@ -56,8 +63,9 @@ function scaleIframeContent() { // Get the width & height of the content inside the iframe. const contentWidth = contentElement.clientWidth; - const contentHeight = contentElement.clientHeight + iframeHeight * (PERCENT_VERTICAL_PADDING / 100) * 2; - iframe.style.paddingTop = iframeHeight * (PERCENT_VERTICAL_PADDING / 100) + "px" + const contentHeight = contentElement.clientHeight + iframeHeight * (PERCENT_VERTICAL_PADDING / 100) * 2 + - PADDING_OFFSETS_PX[window.location.pathname]; + iframe.style.paddingTop = iframeHeight * (PERCENT_VERTICAL_PADDING / 100) - PADDING_OFFSETS_PX[window.location.pathname] + "px" // Calculate the scale factor based on the smallest ratio between available window size and content size. const scale = Math.min(iframeWidth / contentWidth, iframeHeight / contentHeight); diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css index c3c0ea2723..bf9c84bf39 100644 --- a/public/stylesheets/main.css +++ b/public/stylesheets/main.css @@ -265,22 +265,6 @@ footer { height: auto; } -#mini-footer-audit { - position: relative; - width: 100%; - min-height: 40px; - text-align: center; - color: #585c63; - font-size: 12px; - line-height: 1.6em; - font-family: raleway, sans-serif; -} - -#mini-footer-audit a { - color:#585c63; - text-decoration:underline; -} - #footer-container { position: relative; top: 00px; diff --git a/public/stylesheets/wrapperFrame.css b/public/stylesheets/wrapperFrame.css index 7fe4c1e543..57c378ede8 100644 --- a/public/stylesheets/wrapperFrame.css +++ b/public/stylesheets/wrapperFrame.css @@ -9,7 +9,3 @@ html, body { border: none; transform-origin: 0 0; } - -#mini-footer-audit { - display: none; -} From 7aff3cdf74c65733653b505b0bf65b013fdc6a55 Mon Sep 17 00:00:00 2001 From: John O'Meara Date: Tue, 26 Nov 2024 03:16:12 +0000 Subject: [PATCH 19/24] Improved padding on newValidateBeta --- public/javascripts/common/wrapperFrame.js | 7 +++++-- public/stylesheets/newValidateBeta.css | 2 +- public/stylesheets/wrapperFrame.css | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index 626ffabd05..d1aa6fc3a9 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -1,4 +1,4 @@ -const PERCENT_VERTICAL_PADDING = 5 +const PERCENT_VERTICAL_PADDING = 7 // These offsets are in terms of iframe context pixels, not the current page pixels. const PADDING_OFFSETS_PX = { @@ -65,7 +65,10 @@ function scaleIframeContent() { const contentWidth = contentElement.clientWidth; const contentHeight = contentElement.clientHeight + iframeHeight * (PERCENT_VERTICAL_PADDING / 100) * 2 - PADDING_OFFSETS_PX[window.location.pathname]; - iframe.style.paddingTop = iframeHeight * (PERCENT_VERTICAL_PADDING / 100) - PADDING_OFFSETS_PX[window.location.pathname] + "px" + iframe.style.paddingTop = Math.max(0, iframeHeight * (PERCENT_VERTICAL_PADDING / 100) - PADDING_OFFSETS_PX[window.location.pathname]) + "px" + if((iframeHeight * (PERCENT_VERTICAL_PADDING / 100) - PADDING_OFFSETS_PX[window.location.pathname]) < 0) { + iframeDocument.body.style.marginTop = iframeHeight * (PERCENT_VERTICAL_PADDING / 100) - PADDING_OFFSETS_PX[window.location.pathname] + "px" + } // Calculate the scale factor based on the smallest ratio between available window size and content size. const scale = Math.min(iframeWidth / contentWidth, iframeHeight / contentHeight); diff --git a/public/stylesheets/newValidateBeta.css b/public/stylesheets/newValidateBeta.css index 276a4e1f8e..222dddfb18 100644 --- a/public/stylesheets/newValidateBeta.css +++ b/public/stylesheets/newValidateBeta.css @@ -3,7 +3,7 @@ width: 1080px; } #svv-application-holder { - height: 635px; + height: 600px; width: 1050px; } #svv-panorama-holder { diff --git a/public/stylesheets/wrapperFrame.css b/public/stylesheets/wrapperFrame.css index 57c378ede8..c8f8303898 100644 --- a/public/stylesheets/wrapperFrame.css +++ b/public/stylesheets/wrapperFrame.css @@ -8,4 +8,5 @@ html, body { height: calc(100vh - 70px); border: none; transform-origin: 0 0; + overflow: hidden; } From 88373ccc049f88efefda6fc7b0f5129250a8a03f Mon Sep 17 00:00:00 2001 From: John O'Meara Date: Tue, 26 Nov 2024 03:24:31 +0000 Subject: [PATCH 20/24] Increase width of tool-ui in newValidateBeta --- public/stylesheets/newValidateBeta.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/stylesheets/newValidateBeta.css b/public/stylesheets/newValidateBeta.css index 222dddfb18..e3f1ba8ae7 100644 --- a/public/stylesheets/newValidateBeta.css +++ b/public/stylesheets/newValidateBeta.css @@ -1,6 +1,6 @@ /* Overriding defaults from Validate. */ .tool-ui { - width: 1080px; + width: 1170px; } #svv-application-holder { height: 600px; From 86f48ce85e9ffd912daeaee0cafb0fc93ddb6f03 Mon Sep 17 00:00:00 2001 From: Mikey Saugstad Date: Mon, 2 Dec 2024 12:05:06 -0700 Subject: [PATCH 21/24] updates Validate page mission header --- public/locales/de/validate.json | 2 +- public/locales/en/validate.json | 2 +- public/locales/es/validate.json | 2 +- public/locales/nl/validate.json | 2 +- public/locales/zh-TW/validate.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/locales/de/validate.json b/public/locales/de/validate.json index 6dbc655566..69573f5495 100644 --- a/public/locales/de/validate.json +++ b/public/locales/de/validate.json @@ -141,7 +141,7 @@ "no-new-mission-button": "Validieren in" }, "mission-start-tutorial": { - "mst-instruction-1": "IHRE MISSION", + "mst-instruction-1": "MISSION VALIDIEREN", "mst-instruction-2": "Validieren Sie {{nLabels}} Beschriftungen von {{- labelType}}", "example-type-label-incorrect": "FALSCHES BEISPIEL", "label-on-image-title-incorrect": "Falsche Beschriftung", diff --git a/public/locales/en/validate.json b/public/locales/en/validate.json index 5fc2f17ce4..3167f63920 100644 --- a/public/locales/en/validate.json +++ b/public/locales/en/validate.json @@ -210,7 +210,7 @@ "no-new-mission-button": "Validate in" }, "mission-start-tutorial": { - "mst-instruction-1": "YOUR MISSION", + "mst-instruction-1": "VALIDATE MISSION", "mst-instruction-2": "Validate {{nLabels}} {{- labelType}} Labels", "example-type-label-incorrect": "WRONG EXAMPLE", "label-on-image-title-incorrect": "Wrong label", diff --git a/public/locales/es/validate.json b/public/locales/es/validate.json index e14d42f8e6..b2483da290 100644 --- a/public/locales/es/validate.json +++ b/public/locales/es/validate.json @@ -141,7 +141,7 @@ "no-new-mission-button": "Validar en" }, "mission-start-tutorial": { - "mst-instruction-1": "TU MISIÓN", + "mst-instruction-1": "VALIDAR MISIÓN", "mst-instruction-2": "Validar {{nLabels}} etiquetas de {{- labelType}}", "example-type-label-incorrect": "EJEMPLO INCORRECTO", "label-on-image-title-incorrect": "etiqueta incorrecta", diff --git a/public/locales/nl/validate.json b/public/locales/nl/validate.json index cf51e5bbab..c5eb43349e 100644 --- a/public/locales/nl/validate.json +++ b/public/locales/nl/validate.json @@ -141,7 +141,7 @@ "no-new-mission-button": "Valideren in" }, "mission-start-tutorial": { - "mst-instruction-1": "JOUW MISSIE", + "mst-instruction-1": "VALIDEER MISSIE", "mst-instruction-2": "Valideer {{nLabels}} {{- labelType}} labels", "example-type-label-incorrect": "VERKEERD VOORBEELD", "label-on-image-title-incorrect": "Verkeerd etiket", diff --git a/public/locales/zh-TW/validate.json b/public/locales/zh-TW/validate.json index d8b30fa1e4..9c735a6740 100644 --- a/public/locales/zh-TW/validate.json +++ b/public/locales/zh-TW/validate.json @@ -141,7 +141,7 @@ "no-new-mission-button": "在中驗證" }, "mission-start-tutorial": { - "mst-instruction-1": "您的任務", + "mst-instruction-1": "驗證任務", "mst-instruction-2": "檢核{{nLabels}}個{{- labelType}}的標記", "example-type-label-incorrect": "錯誤案例", "label-on-image-title-incorrect": "錯誤標記", From 484baf1984472343a921eadbe57b0225a1b0fc0a Mon Sep 17 00:00:00 2001 From: Mikey Saugstad Date: Mon, 2 Dec 2024 12:17:25 -0700 Subject: [PATCH 22/24] small UI padding adjustments --- public/javascripts/common/wrapperFrame.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index d1aa6fc3a9..71502cda48 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -1,9 +1,9 @@ -const PERCENT_VERTICAL_PADDING = 7 +const PERCENT_VERTICAL_PADDING = 6; // These offsets are in terms of iframe context pixels, not the current page pixels. const PADDING_OFFSETS_PX = { "/explore": 13, - "/validate": 28, + "/validate": 25, "/newValidateBeta": 35 } From 72ec63f62ed7ef2a7ee773e3c3e60e757d185707 Mon Sep 17 00:00:00 2001 From: Mikey Saugstad Date: Mon, 2 Dec 2024 12:20:26 -0700 Subject: [PATCH 23/24] adds automatic zoom to AdminValidate --- app/controllers/ValidationController.scala | 88 ++++++++++++--------- app/views/adminValidationWrapper.scala.html | 11 +++ public/javascripts/common/wrapperFrame.js | 3 +- 3 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 app/views/adminValidationWrapper.scala.html diff --git a/app/controllers/ValidationController.scala b/app/controllers/ValidationController.scala index 808e775af9..dc2323cbce 100644 --- a/app/controllers/ValidationController.scala +++ b/app/controllers/ValidationController.scala @@ -128,6 +128,18 @@ class ValidationController @Inject() (implicit val env: Environment[User, Sessio } } + /** + * Returns /adminValidate wrapper page. + */ + def adminValidationWrapper = UserAwareAction.async { implicit request => + request.identity match { + case Some(user) => + Future.successful(Ok(views.html.adminValidationWrapper("Sidewalk - AdminValidate", Some(user)))) + case None => + Future.successful(Redirect("/anonSignUp?url=/adminValidate")) + } + } + /** * Returns an admin version of the validation page. * @param labelType Label type or label type ID to validate. @@ -136,43 +148,47 @@ class ValidationController @Inject() (implicit val env: Environment[User, Sessio */ def adminValidate(labelType: Option[String], users: Option[String], neighborhoods: Option[String]) = UserAwareAction.async { implicit request => if (isAdmin(request.identity)) { - // If any inputs are invalid, send back error message. For each input, we check if the input is an integer - // representing a valid ID (label_type_id, user_id, or region_id) or a String representing a valid name for that - // parameter (label_type, username, or region_name). - val possibleLabTypeIds: List[Int] = LabelTable.valLabelTypeIds - val parsedLabelTypeId: Option[Option[Int]] = labelType.map { lType => - val parsedId: Try[Int] = Try(lType.toInt) - val lTypeIdFromName: Option[Int] = LabelTypeTable.labelTypeToId(lType) - if (parsedId.isSuccess && possibleLabTypeIds.contains(parsedId.get)) parsedId.toOption - else if (lTypeIdFromName.isDefined) lTypeIdFromName - else None - } - val userIdsList: Option[List[Option[String]]] = users.map(_.split(',').map(_.trim).map { userStr => - val parsedUserId: Option[UUID] = Try(UUID.fromString(userStr)).toOption - val user: Option[DBUser] = parsedUserId.flatMap(u => UserTable.findById(u)) - val userId: Option[String] = UserTable.find(userStr).map(_.userId) - if (user.isDefined) Some(userStr) else if (userId.isDefined) Some(userId.get) else None - }.toList) - val neighborhoodIdList: Option[List[Option[Int]]] = neighborhoods.map(_.split(",").map { regionStr => - val parsedRegionId: Try[Int] = Try(regionStr.toInt) - val regionFromName: Option[Region] = RegionTable.getRegionByName(regionStr) - if (parsedRegionId.isSuccess && RegionTable.getRegion(parsedRegionId.get).isDefined) parsedRegionId.toOption - else if (regionFromName.isDefined) regionFromName.map(_.regionId) - else None - }.toList) - - // If any inputs are invalid (even any item in the list of users/regions), send back error message. - if (parsedLabelTypeId.isDefined && parsedLabelTypeId.get.isEmpty) { - Future.successful(BadRequest(s"Invalid label type provided: ${labelType.get}. Valid label types are: ${LabelTypeTable.getAllLabelTypes.filter(l => possibleLabTypeIds.contains(l.labelTypeId)).map(_.labelType).toList.reverse.mkString(", ")}. Or you can use their IDs: ${possibleLabTypeIds.mkString(", ")}.")) - } else if (userIdsList.isDefined && userIdsList.get.length != userIdsList.get.flatten.length) { - Future.successful(BadRequest(s"One or more of the users provided were not found; please double check your list of users! You can use either their usernames or user IDs. You provided: ${users.get}")) - } else if (neighborhoodIdList.isDefined && neighborhoodIdList.get.length != neighborhoodIdList.get.flatten.length) { - Future.successful(BadRequest(s"One or more of the neighborhoods provided were not found; please double check your list of neighborhoods! You can use either their names or IDs. You provided: ${neighborhoods.get}")) + if (request.headers.get("Sec-Fetch-Dest").getOrElse("") == "document") { + adminValidationWrapper.apply(request) } else { - // If all went well, load the data for Admin Validate with the specified filters. - val adminParams: AdminValidateParams = AdminValidateParams(adminVersion = true, parsedLabelTypeId.flatten, userIdsList.map(_.flatten), neighborhoodIdList.map(_.flatten)) - val validationData = getDataForValidationPages(request, labelCount=10, "Visit_AdminValidate", adminParams) - Future.successful(Ok(views.html.validation("Sidewalk - Admin Validate", request.identity, adminParams, validationData._1, validationData._2, validationData._3, validationData._4.numComplete, validationData._5, validationData._6, validationData._7))) + // If any inputs are invalid, send back error message. For each input, we check if the input is an integer + // representing a valid ID (label_type_id, user_id, or region_id) or a String representing a valid name for that + // parameter (label_type, username, or region_name). + val possibleLabTypeIds: List[Int] = LabelTable.valLabelTypeIds + val parsedLabelTypeId: Option[Option[Int]] = labelType.map { lType => + val parsedId: Try[Int] = Try(lType.toInt) + val lTypeIdFromName: Option[Int] = LabelTypeTable.labelTypeToId(lType) + if (parsedId.isSuccess && possibleLabTypeIds.contains(parsedId.get)) parsedId.toOption + else if (lTypeIdFromName.isDefined) lTypeIdFromName + else None + } + val userIdsList: Option[List[Option[String]]] = users.map(_.split(',').map(_.trim).map { userStr => + val parsedUserId: Option[UUID] = Try(UUID.fromString(userStr)).toOption + val user: Option[DBUser] = parsedUserId.flatMap(u => UserTable.findById(u)) + val userId: Option[String] = UserTable.find(userStr).map(_.userId) + if (user.isDefined) Some(userStr) else if (userId.isDefined) Some(userId.get) else None + }.toList) + val neighborhoodIdList: Option[List[Option[Int]]] = neighborhoods.map(_.split(",").map { regionStr => + val parsedRegionId: Try[Int] = Try(regionStr.toInt) + val regionFromName: Option[Region] = RegionTable.getRegionByName(regionStr) + if (parsedRegionId.isSuccess && RegionTable.getRegion(parsedRegionId.get).isDefined) parsedRegionId.toOption + else if (regionFromName.isDefined) regionFromName.map(_.regionId) + else None + }.toList) + + // If any inputs are invalid (even any item in the list of users/regions), send back error message. + if (parsedLabelTypeId.isDefined && parsedLabelTypeId.get.isEmpty) { + Future.successful(BadRequest(s"Invalid label type provided: ${labelType.get}. Valid label types are: ${LabelTypeTable.getAllLabelTypes.filter(l => possibleLabTypeIds.contains(l.labelTypeId)).map(_.labelType).toList.reverse.mkString(", ")}. Or you can use their IDs: ${possibleLabTypeIds.mkString(", ")}.")) + } else if (userIdsList.isDefined && userIdsList.get.length != userIdsList.get.flatten.length) { + Future.successful(BadRequest(s"One or more of the users provided were not found; please double check your list of users! You can use either their usernames or user IDs. You provided: ${users.get}")) + } else if (neighborhoodIdList.isDefined && neighborhoodIdList.get.length != neighborhoodIdList.get.flatten.length) { + Future.successful(BadRequest(s"One or more of the neighborhoods provided were not found; please double check your list of neighborhoods! You can use either their names or IDs. You provided: ${neighborhoods.get}")) + } else { + // If all went well, load the data for Admin Validate with the specified filters. + val adminParams: AdminValidateParams = AdminValidateParams(adminVersion = true, parsedLabelTypeId.flatten, userIdsList.map(_.flatten), neighborhoodIdList.map(_.flatten)) + val validationData = getDataForValidationPages(request, labelCount = 10, "Visit_AdminValidate", adminParams) + Future.successful(Ok(views.html.validation("Sidewalk - Admin Validate", request.identity, adminParams, validationData._1, validationData._2, validationData._3, validationData._4.numComplete, validationData._5, validationData._6, validationData._7))) + } } } else { Future.failed(new AuthenticationException("User is not an administrator")) diff --git a/app/views/adminValidationWrapper.scala.html b/app/views/adminValidationWrapper.scala.html new file mode 100644 index 0000000000..a1c27492a1 --- /dev/null +++ b/app/views/adminValidationWrapper.scala.html @@ -0,0 +1,11 @@ +@import models.user.User +@(title: String, user: Option[User] = None)(implicit lang: Lang) + +@main(title, Some("/validate")) { + @navbar(user, Some("/adminValidate")) + + + + + +} diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index 71502cda48..14574700b9 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -4,7 +4,8 @@ const PERCENT_VERTICAL_PADDING = 6; const PADDING_OFFSETS_PX = { "/explore": 13, "/validate": 25, - "/newValidateBeta": 35 + "/newValidateBeta": 35, + "/adminValidate": 25 } const iframe = document.getElementById('wrapper-frame'); From b821ae220523d9205d86a099969d1e66a7d1ae48 Mon Sep 17 00:00:00 2001 From: Mikey Saugstad Date: Mon, 2 Dec 2024 16:20:14 -0700 Subject: [PATCH 24/24] final cleanup for auto resizing explore/validate --- app/views/adminValidationWrapper.scala.html | 2 +- app/views/gallery.scala.html | 2 +- app/views/main.scala.html | 2 +- app/views/newValidateBetaWrapper.scala.html | 2 +- public/javascripts/common/Utilities.js | 31 --------------------- public/javascripts/common/wrapperFrame.js | 6 ++-- public/stylesheets/main.css | 16 +++++++++++ 7 files changed, 23 insertions(+), 38 deletions(-) diff --git a/app/views/adminValidationWrapper.scala.html b/app/views/adminValidationWrapper.scala.html index a1c27492a1..c0e3da8e76 100644 --- a/app/views/adminValidationWrapper.scala.html +++ b/app/views/adminValidationWrapper.scala.html @@ -2,7 +2,7 @@ @(title: String, user: Option[User] = None)(implicit lang: Lang) @main(title, Some("/validate")) { - @navbar(user, Some("/adminValidate")) + @navbar(user, Some("/validate")) diff --git a/app/views/gallery.scala.html b/app/views/gallery.scala.html index 8cedaf9be3..000db160d2 100644 --- a/app/views/gallery.scala.html +++ b/app/views/gallery.scala.html @@ -3,7 +3,7 @@ @(title: String, user: Option[User] = None, cityInfo: List[CityInfo], labelType: String, labels: List[(String, String)], regionIds: List[Int], severities: List[Int], tags: List[String], valOptions: List[String])(implicit lang: Lang) @currentCity = @{cityInfo.filter(c => c.current).head} -@main(title) { +@main(title, Some("/gallery")) { diff --git a/app/views/main.scala.html b/app/views/main.scala.html index 6c0c10befe..acfa7e9d7d 100644 --- a/app/views/main.scala.html +++ b/app/views/main.scala.html @@ -91,7 +91,7 @@ document.getElementsByTagName("body")[0].style.overflow = "hidden"; } - + @if(url.get == "/explore" || url.get == "/validate" || url.get == "/newValidateBeta" || url.get == "/signInMobile" || url.get == "/signUpMobile") { @if(url.get == "/explore" || url.get == "/validate" || url.get == "/newValidateBeta") { } else { diff --git a/app/views/newValidateBetaWrapper.scala.html b/app/views/newValidateBetaWrapper.scala.html index 75938d963f..272ea7100d 100644 --- a/app/views/newValidateBetaWrapper.scala.html +++ b/app/views/newValidateBetaWrapper.scala.html @@ -1,7 +1,7 @@ @import models.user.User @(title: String, user: Option[User] = None)(implicit lang: Lang) -@main(title, Some("/validate")) { +@main(title, Some("/newValidateBeta")) { @navbar(user, Some("/newValidateBeta")) diff --git a/public/javascripts/common/Utilities.js b/public/javascripts/common/Utilities.js index b0d7a86063..848c005542 100644 --- a/public/javascripts/common/Utilities.js +++ b/public/javascripts/common/Utilities.js @@ -162,37 +162,6 @@ function camelToKebab(theString) { } util.camelToKebab = camelToKebab; -// Returns true if the element is fully visible, false otherwise. Takes into account CSS zoom (tested on chrome/safari). -function _isVisible(elem) { - var zoomFactor = parseFloat(elem.style.zoom) / 100.0 || 1; - var scaledRect = elem.getBoundingClientRect(); - if (zoomFactor !== 1) { - scaledRect = { - left: scaledRect.left * zoomFactor, - bottom: scaledRect.bottom * zoomFactor, - right: scaledRect.right * zoomFactor - }; - } - return scaledRect.left >= 0 && - scaledRect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && - scaledRect.right <= (window.innerWidth || document.documentElement.clientWidth); -} - -// Finds the maximum CSS zoom level for an element (tested on chrome/safari). -function _findMaxZoomLevel(elem, startZoom) { - var zoomPercent = startZoom; - elem.style.zoom = zoomPercent + '%'; - while (_isVisible(elem) && zoomPercent < 500) { - zoomPercent += 10; - elem.style.zoom = zoomPercent + '%'; - } - while (!_isVisible(elem) && zoomPercent > 10) { - zoomPercent -= 1; - elem.style.zoom = zoomPercent + '%'; - } - return zoomPercent; -} - function escapeHTML(str) { return str.replace(/[&<>"']/g, function(match) { switch (match) { diff --git a/public/javascripts/common/wrapperFrame.js b/public/javascripts/common/wrapperFrame.js index 14574700b9..51d4ca8ddf 100644 --- a/public/javascripts/common/wrapperFrame.js +++ b/public/javascripts/common/wrapperFrame.js @@ -67,7 +67,7 @@ function scaleIframeContent() { const contentHeight = contentElement.clientHeight + iframeHeight * (PERCENT_VERTICAL_PADDING / 100) * 2 - PADDING_OFFSETS_PX[window.location.pathname]; iframe.style.paddingTop = Math.max(0, iframeHeight * (PERCENT_VERTICAL_PADDING / 100) - PADDING_OFFSETS_PX[window.location.pathname]) + "px" - if((iframeHeight * (PERCENT_VERTICAL_PADDING / 100) - PADDING_OFFSETS_PX[window.location.pathname]) < 0) { + if ((iframeHeight * (PERCENT_VERTICAL_PADDING / 100) - PADDING_OFFSETS_PX[window.location.pathname]) < 0) { iframeDocument.body.style.marginTop = iframeHeight * (PERCENT_VERTICAL_PADDING / 100) - PADDING_OFFSETS_PX[window.location.pathname] + "px" } @@ -108,10 +108,10 @@ setInterval(() => { // This line is needed to lock the scroll to the top in rare cases where it can get messed up. window.scrollTo(0, 0); - // Passthrough the 'svl'/'svv' variables from the iframe's window object into the main window object. + // Pass through the 'svl'/'svv' vars from the iframe's window object into the main window object. if (iframe.contentWindow.svl) window.svl = iframe.contentWindow.svl; if (iframe.contentWindow.svv) window.svv = iframe.contentWindow.svv; - // Passthrough the 'InitialMissionInstruction' variable from the iframe's window object into the main window object. + // Pass through the 'InitialMissionInstruction' var from the iframe's window object into the main window object. window.InitialMissionInstruction = iframe.contentWindow.InitialMissionInstruction; }, 100); diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css index bf9c84bf39..f1ee20b074 100644 --- a/public/stylesheets/main.css +++ b/public/stylesheets/main.css @@ -265,6 +265,22 @@ footer { height: auto; } +#mini-footer-audit { + position: relative; + width: 100%; + min-height: 40px; + text-align: center; + color: #585c63; + font-size: 12px; + line-height: 1.6em; + font-family: raleway, sans-serif; +} + +#mini-footer-audit a { + color:#585c63; + text-decoration:underline; +} + #footer-container { position: relative; top: 00px;