diff --git a/.gitignore b/.gitignore index 922ae81a..2a91d5e7 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,6 @@ target/classes/templates/fragments/footer.html target/classes/templates/fragments/head.html target/classes/templates/fragments/nav.html target/classes/templates/index.html +/database/hsqldb/bin/test.log +/database/hsqldb/bin/test.properties +/database/hsqldb/bin/test.script diff --git a/src/main/java/com/uniovi/components/MultipleQuestionGenerator.java b/src/main/java/com/uniovi/components/MultipleQuestionGenerator.java new file mode 100644 index 00000000..d8409cc5 --- /dev/null +++ b/src/main/java/com/uniovi/components/MultipleQuestionGenerator.java @@ -0,0 +1,24 @@ +package com.uniovi.components; + +import com.uniovi.components.generators.QuestionGenerator; +import com.uniovi.entities.Category; +import com.uniovi.entities.Question; + +import java.util.ArrayList; +import java.util.List; + +public class MultipleQuestionGenerator { + private QuestionGenerator[] generators; + + public MultipleQuestionGenerator(QuestionGenerator... generators) { + this.generators = generators; + } + + public List getQuestions() { + List questions = new ArrayList<>(); + for (QuestionGenerator generator : generators) { + questions.addAll(generator.getQuestions()); + } + return questions; + } +} diff --git a/src/main/java/com/uniovi/components/QuestionGeneratorTestController.java b/src/main/java/com/uniovi/components/QuestionGeneratorTestController.java index 82ba147e..bb954701 100644 --- a/src/main/java/com/uniovi/components/QuestionGeneratorTestController.java +++ b/src/main/java/com/uniovi/components/QuestionGeneratorTestController.java @@ -11,14 +11,11 @@ @RestController public class QuestionGeneratorTestController { - @Autowired - CapitalQuestionGenerator qgen; - - @RequestMapping("/test") + /*@RequestMapping("/test") public void test() { List q = qgen.getQuestions(); for(Question question : q){ System.out.println(question); } - } + }*/ } diff --git a/src/main/java/com/uniovi/components/generators/AbstractQuestionGenerator.java b/src/main/java/com/uniovi/components/generators/AbstractQuestionGenerator.java index cb4d46ab..81006a95 100644 --- a/src/main/java/com/uniovi/components/generators/AbstractQuestionGenerator.java +++ b/src/main/java/com/uniovi/components/generators/AbstractQuestionGenerator.java @@ -6,6 +6,9 @@ import com.uniovi.entities.Category; import com.uniovi.entities.Question; import com.uniovi.services.CategoryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; import java.net.URI; import java.net.URLEncoder; @@ -17,11 +20,11 @@ import java.util.List; public abstract class AbstractQuestionGenerator implements QuestionGenerator{ - private List questions = new ArrayList<>(); protected final CategoryService categoryService; private String query; protected String statement; + protected String language; protected AbstractQuestionGenerator(CategoryService categoryService) { this.categoryService = categoryService; @@ -38,7 +41,7 @@ public void questionGenerator(String statement, List options, String cor Answer correct = new Answer(correctAnswer, true); answers.add(correct); - Question question = new Question(statement, answers, correct, category); + Question question = new Question(statement, answers, correct, category, language); question.scrambleOptions(); questions.add(question); } @@ -84,5 +87,4 @@ public List getQuestions() { protected abstract String generateCorrectAnswer(JsonNode result); protected abstract String getQuestionSubject(JsonNode result); - } diff --git a/src/main/java/com/uniovi/components/generators/geography/BorderQuestionGenerator.java b/src/main/java/com/uniovi/components/generators/geography/BorderQuestionGenerator.java index 1bbd4475..f791f1e5 100644 --- a/src/main/java/com/uniovi/components/generators/geography/BorderQuestionGenerator.java +++ b/src/main/java/com/uniovi/components/generators/geography/BorderQuestionGenerator.java @@ -6,12 +6,18 @@ import java.util.*; public class BorderQuestionGenerator extends AbstractGeographyGenerator{ - + private static final Map STATEMENTS = new HashMap<>() { + { + put("en", "Which countries share a border with "); + put("es", "¿Con qué países comparte frontera "); + } + }; private Set usedCountries = new HashSet<>(); - public BorderQuestionGenerator(CategoryService categoryService) { + public BorderQuestionGenerator(CategoryService categoryService, String language) { super(categoryService); - this.statement = "Which countries share a border with "; + this.statement = STATEMENTS.get(language); + this.language = language; } private List getAllBorderingCountries(JsonNode resultsNode, String correctCountry) { @@ -64,7 +70,7 @@ public String getQuery() { " FILTER NOT EXISTS {?country wdt:P31 wd:Q3024240}" + " FILTER NOT EXISTS {?country wdt:P31 wd:Q28171280}" + " ?country wdt:P47 ?borderingCountry ." + - " SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE],en\" }" + + " SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE]," + language + "\" }" + "}"; } } diff --git a/src/main/java/com/uniovi/components/generators/geography/CapitalQuestionGenerator.java b/src/main/java/com/uniovi/components/generators/geography/CapitalQuestionGenerator.java index 951292fc..e5d91727 100644 --- a/src/main/java/com/uniovi/components/generators/geography/CapitalQuestionGenerator.java +++ b/src/main/java/com/uniovi/components/generators/geography/CapitalQuestionGenerator.java @@ -2,18 +2,23 @@ import com.fasterxml.jackson.databind.JsonNode; import com.uniovi.services.CategoryService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; +import java.util.*; -@Component public class CapitalQuestionGenerator extends AbstractGeographyGenerator{ + private static final Map STATEMENTS = new HashMap<>() { + { + put("en", "What is the capital of "); + put("es", "¿Cuál es la capital de "); + } + }; - public CapitalQuestionGenerator(CategoryService categoryService) { + public CapitalQuestionGenerator(CategoryService categoryService, String language) { super(categoryService); - this.statement = "What is the capital of "; + this.statement = STATEMENTS.get(language); + this.language = language; } @Override @@ -24,7 +29,7 @@ public String getQuery() { " FILTER NOT EXISTS {?country wdt:P31 wd:Q3024240}" + " FILTER NOT EXISTS {?country wdt:P31 wd:Q28171280}" + " OPTIONAL { ?country wdt:P36 ?capital } ." + - " SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE],en\" }" + + " SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE]," + language + "\" }" + "}" + "ORDER BY ?countryLabel"; } diff --git a/src/main/java/com/uniovi/components/generators/geography/ContinentQuestionGeneration.java b/src/main/java/com/uniovi/components/generators/geography/ContinentQuestionGeneration.java index 7e4ccbfb..b1d476ae 100644 --- a/src/main/java/com/uniovi/components/generators/geography/ContinentQuestionGeneration.java +++ b/src/main/java/com/uniovi/components/generators/geography/ContinentQuestionGeneration.java @@ -4,14 +4,20 @@ import com.uniovi.services.CategoryService; import org.springframework.scheduling.annotation.Scheduled; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; +import java.util.*; public class ContinentQuestionGeneration extends AbstractGeographyGenerator{ - public ContinentQuestionGeneration(CategoryService categoryService) { + private static final Map STATEMENTS = new HashMap<>() { + { + put("en", "In which continent is "); + put("es", "¿En qué continente se encuentra "); + } + }; + + public ContinentQuestionGeneration(CategoryService categoryService, String language) { super(categoryService); - this.statement = "In which continent is "; + this.statement = STATEMENTS.get(language); + this.language = language; } private List getAllContinents(JsonNode resultsNode, String correctContinent) { @@ -63,7 +69,7 @@ public String getQuery() { " FILTER NOT EXISTS {?country wdt:P31 wd:Q3024240}" + " FILTER NOT EXISTS {?country wdt:P31 wd:Q28171280}" + " OPTIONAL { ?country wdt:P30 ?continent } ." + - " SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE],en\" }" + + " SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE]," + language + "\" }" + "}" + "ORDER BY ?countryLabel"; } diff --git a/src/main/java/com/uniovi/configuration/CustomConfiguration.java b/src/main/java/com/uniovi/configuration/CustomConfiguration.java index 86bc6ad0..dbb9eb11 100644 --- a/src/main/java/com/uniovi/configuration/CustomConfiguration.java +++ b/src/main/java/com/uniovi/configuration/CustomConfiguration.java @@ -1,5 +1,8 @@ package com.uniovi.configuration; +import jakarta.persistence.EntityManagerFactory; +import org.hibernate.SessionFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.domain.PageRequest; diff --git a/src/main/java/com/uniovi/configuration/SecurityConfig.java b/src/main/java/com/uniovi/configuration/SecurityConfig.java index 5782663f..0a6a2ec5 100644 --- a/src/main/java/com/uniovi/configuration/SecurityConfig.java +++ b/src/main/java/com/uniovi/configuration/SecurityConfig.java @@ -41,9 +41,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/css/**", "/img/**", "/script/**").permitAll() - .requestMatchers("/home").authenticated() + .requestMatchers("/home/**").authenticated() .requestMatchers("/signup/**").permitAll() .requestMatchers("/api/**").permitAll() + .requestMatchers("/game/**").authenticated() + .requestMatchers("/ranking/playerRanking").authenticated() .requestMatchers("/**").permitAll() ).formLogin( form -> form @@ -69,7 +71,6 @@ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception /** * Method to check if the user is authenticated - * @param request HttpServletRequest * @return boolean */ public static boolean isAuthenticated() { diff --git a/src/main/java/com/uniovi/controllers/GameController.java b/src/main/java/com/uniovi/controllers/GameController.java new file mode 100644 index 00000000..beed332c --- /dev/null +++ b/src/main/java/com/uniovi/controllers/GameController.java @@ -0,0 +1,182 @@ +package com.uniovi.controllers; + +import com.uniovi.entities.GameSession; +import com.uniovi.entities.Player; +import com.uniovi.entities.Question; +import com.uniovi.services.GameSessionService; +import com.uniovi.services.PlayerService; +import com.uniovi.services.QuestionService; +import jakarta.servlet.http.HttpSession; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.security.Principal; +import java.time.Duration; +import java.time.LocalDateTime; + +@Controller +public class GameController { + private final QuestionService questionService; + private final GameSessionService gameSessionService; + private final PlayerService playerService; + + public GameController(QuestionService questionService, GameSessionService gameSessionService, + PlayerService playerService) { + this.questionService = questionService; + this.gameSessionService = gameSessionService; + this.playerService = playerService; + } + + + /** + * This method is used to get the game view and to start the game + * @param model The model to be used + * @return The view to be shown + */ + @GetMapping("/game") + public String getGame(HttpSession session, Model model, Principal principal) { + GameSession gameSession = (GameSession) session.getAttribute("gameSession"); + if (gameSession != null) { + if (checkUpdateGameSession(gameSession, session)) { + return "game/fragments/gameFinished"; + } + } else { + gameSession = gameSessionService.startNewGame(getLoggedInPlayer(principal)); + session.setAttribute("gameSession", gameSession); + } + + model.addAttribute("question", gameSession.getCurrentQuestion()); + model.addAttribute("questionDuration", getRemainingTime(gameSession)); + return "game/basicGame"; + } + + + /** + * This method is used to check the answer for a specific question + * @param idQuestion The id of the question. + * @param idAnswer The id of the answer. If the id is -1, it means that the user has not selected any answer and the + * time has run out. + * @param model The model to be used. + * @return The view to be shown, if the answer is correct, the success view is shown, otherwise the failure view is + * shown or the timeOutFailure view is shown. + */ + @GetMapping("/game/{idQuestion}/{idAnswer}") + public String getCheckResult(@PathVariable Long idQuestion, @PathVariable Long idAnswer, Model model, HttpSession session) { + GameSession gameSession = (GameSession) session.getAttribute("gameSession"); + if (gameSession == null) { + return "redirect:/game"; + } + + if (!gameSession.hasQuestionId(idQuestion)) { + model.addAttribute("score", gameSession.getScore()); + session.removeAttribute("gameSession"); + return "redirect:/game"; // if someone wants to exploit the game, just redirect to the game page + } + + if(idAnswer == -1 + || getRemainingTime(gameSession) <= 0) { + model.addAttribute("correctAnswer", gameSession.getCurrentQuestion().getCorrectAnswer()); + model.addAttribute("messageKey", "timeRunOut.result"); + model.addAttribute("logoImage", "/images/logo_incorrect.png"); + gameSession.addAnsweredQuestion(gameSession.getCurrentQuestion()); + gameSession.addQuestion(false, 0); + } + else if(questionService.checkAnswer(idQuestion, idAnswer)) { + model.addAttribute("messageKey", "correctAnswer.result"); + model.addAttribute("logoImage", "/images/logo_correct.png"); + + if (!gameSession.isAnswered(gameSession.getCurrentQuestion())) { + gameSession.addQuestion(true, getRemainingTime(gameSession)); + gameSession.addAnsweredQuestion(gameSession.getCurrentQuestion()); + } + + } else { + model.addAttribute("correctAnswer", gameSession.getCurrentQuestion().getCorrectAnswer()); + model.addAttribute("messageKey", "failedAnswer.result"); + model.addAttribute("logoImage", "/images/logo_incorrect.png"); + gameSession.addAnsweredQuestion(gameSession.getCurrentQuestion()); + gameSession.addQuestion(false, 0); + } + + session.setAttribute("hasJustAnswered", true); + gameSession.getNextQuestion(); + return "game/fragments/questionResult"; + } + + @GetMapping("/game/update") + public String updateGame(Model model, HttpSession session) { + GameSession gameSession = (GameSession) session.getAttribute("gameSession"); + Question nextQuestion = gameSession.getCurrentQuestion(); + if (nextQuestion == null) { + gameSessionService.endGame(gameSession); + session.removeAttribute("gameSession"); + model.addAttribute("score", gameSession.getScore()); + return "game/fragments/gameFinished"; + } + + if (session.getAttribute("hasJustAnswered") != null) { + if ((boolean) session.getAttribute("hasJustAnswered")) + gameSession.setFinishTime(LocalDateTime.now()); + session.removeAttribute("hasJustAnswered"); + } + model.addAttribute("question", gameSession.getCurrentQuestion()); + model.addAttribute("questionDuration", getRemainingTime(gameSession)); + return "game/fragments/gameFrame"; + } + + @GetMapping("/game/finished/{points}") + public String finishGame(@PathVariable int points, Model model, HttpSession session) { + GameSession gameSession = (GameSession) session.getAttribute("gameSession"); + if (gameSession != null) { + gameSessionService.endGame(gameSession); + session.removeAttribute("gameSession"); + } + model.addAttribute("score", points); + return "game/gameFinished"; + } + + @GetMapping("/game/points") + @ResponseBody + public String getPoints(HttpSession session) { + GameSession gameSession = (GameSession) session.getAttribute("gameSession"); + if (gameSession != null) + return String.valueOf(gameSession.getScore()); + else + return "0"; + } + + private Player getLoggedInPlayer(Principal principal) { + return playerService.getUserByUsername(principal.getName()).get(); + } + + /** + * This method is used to check if the game session has to be updated + * @param gameSession The game session to be checked + * @param session The session to be used + * @return True if the game session has been ended, false otherwise + */ + private boolean checkUpdateGameSession(GameSession gameSession, HttpSession session) { + // if time since last question started is greater than the time per question, add a new question (or check for game finish) + if (getRemainingTime(gameSession) <= 0 + && gameSession.getQuestionsToAnswer().isEmpty() + && gameSession.getCurrentQuestion() != null) { + gameSession.addQuestion(false, 0); + gameSession.addAnsweredQuestion(gameSession.getCurrentQuestion()); + if (gameSession.getQuestionsToAnswer().isEmpty()) { + gameSessionService.endGame(gameSession); + session.removeAttribute("gameSession"); + return true; + } + } + + return false; + } + + private int getRemainingTime(GameSession gameSession) { + return (int) Duration.between(LocalDateTime.now(), + gameSession.getFinishTime().plusSeconds(QuestionService.SECONDS_PER_QUESTION)).toSeconds(); + } +} diff --git a/src/main/java/com/uniovi/controllers/HomeController.java b/src/main/java/com/uniovi/controllers/HomeController.java index 60274ca3..5ae72609 100644 --- a/src/main/java/com/uniovi/controllers/HomeController.java +++ b/src/main/java/com/uniovi/controllers/HomeController.java @@ -1,17 +1,28 @@ package com.uniovi.controllers; +import com.uniovi.entities.ApiKey; +import com.uniovi.entities.Player; +import com.uniovi.services.ApiKeyService; import com.uniovi.services.PlayerService; +import com.uniovi.services.QuestionService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; + +import java.util.Random; @Controller public class HomeController{ private final PlayerService playerService; + private final ApiKeyService apiKeyService; @Autowired - public HomeController(PlayerService playerService) { + public HomeController(PlayerService playerService, ApiKeyService apiKeyService) { this.playerService = playerService; + this.apiKeyService = apiKeyService; } @GetMapping("/") @@ -23,4 +34,20 @@ public String home(){ public String apiHome() { return "api/apiHome"; } + + @GetMapping("/home/apikey") + public String apiKeyHome(Authentication auth, Model model) { + Player player = playerService.getUserByUsername(auth.getName()).get(); + model.addAttribute("apiKey", player.getApiKey()); + return "player/apiKeyHome"; + } + + @GetMapping("/home/apikey/create") + public String createApiKey(Authentication auth) { + Player player = playerService.getUserByUsername(auth.getName()).get(); + if (player.getApiKey() == null) { + apiKeyService.createApiKey(player); + } + return "redirect:/home/apikey"; + } } diff --git a/src/main/java/com/uniovi/controllers/PlayersController.java b/src/main/java/com/uniovi/controllers/PlayersController.java index a998b69d..a32bd5ae 100644 --- a/src/main/java/com/uniovi/controllers/PlayersController.java +++ b/src/main/java/com/uniovi/controllers/PlayersController.java @@ -101,7 +101,7 @@ public String showGlobalRanking(Pageable pageable, Model model) { model.addAttribute("ranking", ranking.getContent()); model.addAttribute("page", ranking); - return "/ranking/globalRanking"; + return "ranking/globalRanking"; } @GetMapping("/ranking/playerRanking") @@ -112,7 +112,7 @@ public String showPlayerRanking(Pageable pageable, Model model, Principal princi model.addAttribute("ranking", ranking.getContent()); model.addAttribute("page", ranking); - return "/ranking/playerRanking"; + return "ranking/playerRanking"; } } diff --git a/src/main/java/com/uniovi/entities/Answer.java b/src/main/java/com/uniovi/entities/Answer.java index 044c90f5..a9528dba 100644 --- a/src/main/java/com/uniovi/entities/Answer.java +++ b/src/main/java/com/uniovi/entities/Answer.java @@ -1,5 +1,7 @@ package com.uniovi.entities; +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -19,9 +21,13 @@ public class Answer implements JsonEntity { @GeneratedValue private Long id; + @JsonIgnore private String text; + + @JsonIgnore private boolean correct; + @JsonIgnore @ManyToOne private Question question; diff --git a/src/main/java/com/uniovi/entities/Associations.java b/src/main/java/com/uniovi/entities/Associations.java index 1d1db3cc..28493169 100644 --- a/src/main/java/com/uniovi/entities/Associations.java +++ b/src/main/java/com/uniovi/entities/Associations.java @@ -1,4 +1,5 @@ package com.uniovi.entities; + import java.util.*; public class Associations { @@ -81,6 +82,7 @@ public static class PlayerGameSession { * @param player The player * @param gameSession The game session */ + public static void addGameSession(Player player, GameSession gameSession) { gameSession.setPlayer(player); player.getGameSessions().add(gameSession); diff --git a/src/main/java/com/uniovi/entities/GameSession.java b/src/main/java/com/uniovi/entities/GameSession.java index 4928b0e3..1a7f7fa3 100644 --- a/src/main/java/com/uniovi/entities/GameSession.java +++ b/src/main/java/com/uniovi/entities/GameSession.java @@ -9,7 +9,9 @@ import lombok.NoArgsConstructor; import lombok.Setter; +import java.time.Duration; import java.time.LocalDateTime; +import java.util.*; @Getter @Setter @@ -34,10 +36,52 @@ public class GameSession implements JsonEntity { private int score; - public void addQuestion(boolean correct) { + @Transient + private Set answeredQuestions = new HashSet<>(); + + @Transient + private List questionsToAnswer = new ArrayList<>(); + + @Transient + private Question currentQuestion; + + public GameSession(Player player, List questions) { + this.player = player; + this.questionsToAnswer = questions; + this.createdAt = LocalDateTime.now(); + this.finishTime = LocalDateTime.now(); + this.correctQuestions = 0; + this.totalQuestions = 0; + getNextQuestion(); + } + + public void addQuestion(boolean correct, int timeLeft) { if(correct) correctQuestions++; totalQuestions++; + + if (correct) { + score += timeLeft + 10 /* magic number TBD */; + } + } + + public void addAnsweredQuestion(Question question) { + questionsToAnswer.remove(question); + answeredQuestions.add(question); + } + + public boolean isAnswered(Question question) { + return answeredQuestions.contains(question); + } + + public Question getNextQuestion() { + if(questionsToAnswer.isEmpty()) { + currentQuestion = null; + return null; + } + Collections.shuffle(questionsToAnswer); + currentQuestion = questionsToAnswer.get(0); + return questionsToAnswer.get(0); } @Override @@ -52,4 +96,29 @@ public JsonNode toJson() { .put("finishTime", finishTime.toString()) .put("score", score); } + + public boolean hasQuestionId(Long idQuestion) { + for (Question q : questionsToAnswer) { + if (q.getId().equals(idQuestion)) + return true; + } + + for (Question q : answeredQuestions) { + if (q.getId().equals(idQuestion)) + return true; + } + return false; + } + + public String getDuration() { + if (createdAt != null && finishTime != null) { + Duration duration = Duration.between(createdAt, finishTime); + long hours = duration.toHours(); + long minutes = duration.toMinutes() % 60; + long seconds = duration.getSeconds() % 60; + return String.format("%02d:%02d:%02d", hours, minutes, seconds); + } else { + return "00:00:00"; + } + } } diff --git a/src/main/java/com/uniovi/entities/Player.java b/src/main/java/com/uniovi/entities/Player.java index 2575c789..824290ef 100644 --- a/src/main/java/com/uniovi/entities/Player.java +++ b/src/main/java/com/uniovi/entities/Player.java @@ -36,7 +36,7 @@ public class Player implements JsonEntity { @ManyToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER) private Set roles = new HashSet<>(); - @OneToMany(mappedBy = "player", cascade = CascadeType.REMOVE) + @OneToMany(mappedBy = "player", cascade = CascadeType.ALL, fetch = FetchType.EAGER) private Set gameSessions = new HashSet<>(); @OneToOne(cascade = CascadeType.ALL, mappedBy = "player") diff --git a/src/main/java/com/uniovi/entities/Question.java b/src/main/java/com/uniovi/entities/Question.java index 143ce474..ee6440f6 100644 --- a/src/main/java/com/uniovi/entities/Question.java +++ b/src/main/java/com/uniovi/entities/Question.java @@ -21,14 +21,17 @@ @Entity @NoArgsConstructor public class Question implements JsonEntity { + public static final String ENGLISH = "en"; + public static final String SPANISH = "es"; + @Id @GeneratedValue private Long id; - @Column(unique = true) + @Column(unique = false) private String statement; - @OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private List options = new ArrayList<>(); @OneToOne @@ -37,12 +40,15 @@ public class Question implements JsonEntity { @ManyToOne private Category category; - public Question(String statement, List options, Answer correctAnswer, Category category) { + private String language; + + public Question(String statement, List options, Answer correctAnswer, Category category, String language) { Assert.isTrue(options.contains(correctAnswer), "Correct answer must be one of the options"); this.statement = statement; Associations.QuestionAnswers.addAnswer(this, options); this.correctAnswer = correctAnswer; this.category = category; + this.language = language; } public void addOption(Answer option) { diff --git a/src/main/java/com/uniovi/repositories/QuestionRepository.java b/src/main/java/com/uniovi/repositories/QuestionRepository.java index b1495aab..16bd18dd 100644 --- a/src/main/java/com/uniovi/repositories/QuestionRepository.java +++ b/src/main/java/com/uniovi/repositories/QuestionRepository.java @@ -1,10 +1,17 @@ package com.uniovi.repositories; import com.uniovi.entities.Question; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import java.util.Optional; - +import java.util.List; public interface QuestionRepository extends CrudRepository { Question findByStatement(String statement); + List findAll(); + + @Query("SELECT q FROM Question q WHERE q.language = ?1") + Page findByLanguage(Pageable pageable, String language); } diff --git a/src/main/java/com/uniovi/services/GameSessionService.java b/src/main/java/com/uniovi/services/GameSessionService.java index 1f032301..e0e69d9a 100644 --- a/src/main/java/com/uniovi/services/GameSessionService.java +++ b/src/main/java/com/uniovi/services/GameSessionService.java @@ -2,13 +2,14 @@ import com.uniovi.entities.GameSession; import com.uniovi.entities.Player; +import com.uniovi.services.impl.GameSessionImpl; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.List; -@Service + public interface GameSessionService { /** @@ -18,17 +19,23 @@ public interface GameSessionService { */ List getGameSessions(); -// /** -// * Return the list of GameSessions by player -// * -// * @return the list of GameSessions by player -// */ -// List getGameSessionsByPlayer(Player player); -// -// HashMap getSortedPlayersScores(); - + /** + * Return the global ranking + * + * @param pageable the pageable + * @return the global ranking + */ + Page getGlobalRanking(Pageable pageable); - public Page getGlobalRanking(Pageable pageable); - public Page getPlayerRanking(Pageable pageable, Player player); + /** + * Return the player ranking + * + * @param pageable the pageable + * @param player the player + * @return the player ranking + */ + Page getPlayerRanking(Pageable pageable, Player player); + GameSession startNewGame(Player player); + void endGame(GameSession gameSession); } diff --git a/src/main/java/com/uniovi/services/InsertSampleDataService.java b/src/main/java/com/uniovi/services/InsertSampleDataService.java index 072d9c69..19c81993 100644 --- a/src/main/java/com/uniovi/services/InsertSampleDataService.java +++ b/src/main/java/com/uniovi/services/InsertSampleDataService.java @@ -1,5 +1,6 @@ package com.uniovi.services; +import com.uniovi.components.MultipleQuestionGenerator; import com.uniovi.components.generators.QuestionGenerator; import com.uniovi.components.generators.geography.BorderQuestionGenerator; import com.uniovi.components.generators.geography.CapitalQuestionGenerator; @@ -8,16 +9,20 @@ import com.uniovi.entities.Associations; import com.uniovi.entities.GameSession; import com.uniovi.entities.Player; +import com.uniovi.entities.Question; import com.uniovi.repositories.GameSessionRepository; import com.uniovi.repositories.QuestionRepository; import jakarta.annotation.PostConstruct; import jakarta.transaction.Transactional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.util.*; @Service public class InsertSampleDataService { @@ -27,6 +32,8 @@ public class InsertSampleDataService { private final QuestionRepository questionRepository; private final GameSessionRepository gameSessionRepository; + private Logger log = LoggerFactory.getLogger(InsertSampleDataService.class);; + public InsertSampleDataService(PlayerService playerService, QuestionService questionService, CategoryService categoryService, QuestionRepository questionRepository, GameSessionRepository gameSessionRepository) { @@ -60,13 +67,22 @@ public void insertSampleQuestions() { questionRepository.deleteAll(); - QuestionGenerator border = new BorderQuestionGenerator(categoryService); - border.getQuestions().forEach(questionService::addNewQuestion); + MultipleQuestionGenerator allQuestionGenerator = new MultipleQuestionGenerator( + new ContinentQuestionGeneration(categoryService, Question.ENGLISH), + new CapitalQuestionGenerator(categoryService, Question.ENGLISH), + new BorderQuestionGenerator(categoryService, Question.ENGLISH) + ); + List questionsEn = allQuestionGenerator.getQuestions(); + questionsEn.forEach(questionService::addNewQuestion); - QuestionGenerator capital = new CapitalQuestionGenerator(categoryService); - capital.getQuestions().forEach(questionService::addNewQuestion); + allQuestionGenerator = new MultipleQuestionGenerator( + new ContinentQuestionGeneration(categoryService, Question.SPANISH), + new CapitalQuestionGenerator(categoryService, Question.SPANISH), + new BorderQuestionGenerator(categoryService, Question.SPANISH) + ); + List questionsEs = allQuestionGenerator.getQuestions(); + questionsEs.forEach(questionService::addNewQuestion); - QuestionGenerator continent = new ContinentQuestionGeneration(categoryService); - continent.getQuestions().forEach(questionService::addNewQuestion); + log.info("Sample questions inserted"); } } diff --git a/src/main/java/com/uniovi/services/QuestionService.java b/src/main/java/com/uniovi/services/QuestionService.java index 9bc96b7d..56b10bbc 100644 --- a/src/main/java/com/uniovi/services/QuestionService.java +++ b/src/main/java/com/uniovi/services/QuestionService.java @@ -9,7 +9,7 @@ @Service public interface QuestionService { - + public static final Integer SECONDS_PER_QUESTION = 25; /** * Add a new question to the database * @@ -31,4 +31,27 @@ public interface QuestionService { * @return The question with the given id */ Optional getQuestion(Long id); + + /** + * Get a random question + * + * @return The question selected + */ + Optional getRandomQuestion(); + + /** + * Get a random question from any category + * + * @param num The number of questions to get + * @return The questions selected + */ + List getRandomQuestions(int num); + + /** + * Check if the answer is correct + * @param idquestion The id of the question + * @param idanswer The id of the answer + * @return True if the answer is correct, false otherwise + */ + boolean checkAnswer(Long idquestion, Long idanswer); } diff --git a/src/main/java/com/uniovi/services/impl/GameSessionImpl.java b/src/main/java/com/uniovi/services/impl/GameSessionImpl.java index caf18492..ed9191b6 100644 --- a/src/main/java/com/uniovi/services/impl/GameSessionImpl.java +++ b/src/main/java/com/uniovi/services/impl/GameSessionImpl.java @@ -1,23 +1,29 @@ package com.uniovi.services.impl; +import com.uniovi.entities.Associations; import com.uniovi.entities.Player; import com.uniovi.repositories.GameSessionRepository; import com.uniovi.entities.GameSession; import com.uniovi.services.GameSessionService; +import com.uniovi.services.QuestionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.*; @Service public class GameSessionImpl implements GameSessionService { + public static final Integer NORMAL_GAME_QUESTION_NUM = 20; private final GameSessionRepository gameSessionRepository; + private final QuestionService questionService; - public GameSessionImpl(GameSessionRepository gameSessionRepository) { + public GameSessionImpl(GameSessionRepository gameSessionRepository, QuestionService questionService) { this.gameSessionRepository = gameSessionRepository; + this.questionService = questionService; } @Override @@ -25,48 +31,6 @@ public List getGameSessions() { return gameSessionRepository.findAll(); } -// @Override -// public List getGameSessionsByPlayer(Player player) { -// return gameSessionRepository.findAllByPlayer(player); -// } -// -// @Override -// public HashMap getSortedPlayersScores() { -// List gameSessions = gameSessionRepository.findAll(); -// HashMap ranking = getRanking(gameSessions); -// // Ordenar las entradas del ranking por puntuación -// List> sortedEntries = new ArrayList<>(ranking.entrySet()); -// sortedEntries.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); -// -// // Crear un LinkedHashMap para mantener el orden de inserción -// LinkedHashMap sortedRanking = new LinkedHashMap<>(); -// for (Map.Entry entry : sortedEntries) { -// sortedRanking.put(entry.getKey(), entry.getValue()); -// } -// -// return sortedRanking; -// } -// -// private static HashMap getRanking(List gameSessions) { -// HashMap ranking = new HashMap<>(); -// -// // Iterar a través de las sesiones de juego -// for (GameSession gameSession : gameSessions) { -// Player player = gameSession.getPlayer(); -// int score = gameSession.getScore(); -// -// // Si el jugador ya está en el ranking, sumar la puntuación, de lo contrario, agregarlo al ranking -// if (ranking.containsKey(player)) { -// int currentScore = ranking.get(player) + score; -// ranking.put(player, currentScore); -// } else { -// ranking.put(player, score); -// } -// } -// return ranking; -// } - - @Override public Page getGlobalRanking(Pageable pageable) { return gameSessionRepository.findTotalScoresByPlayer(pageable); @@ -77,5 +41,15 @@ public Page getPlayerRanking(Pageable pageable, Player player) { return gameSessionRepository.findAllByPlayerOrderByScoreDesc(pageable, player); } + @Override + public GameSession startNewGame(Player player) { + return new GameSession(player, questionService.getRandomQuestions(NORMAL_GAME_QUESTION_NUM)); + } + @Override + public void endGame(GameSession gameSession) { + Associations.PlayerGameSession.addGameSession(gameSession.getPlayer(), gameSession); + gameSession.setFinishTime(LocalDateTime.now()); + gameSessionRepository.save(gameSession); + } } diff --git a/src/main/java/com/uniovi/services/impl/QuestionServiceImpl.java b/src/main/java/com/uniovi/services/impl/QuestionServiceImpl.java index a07139f1..9d04d51a 100644 --- a/src/main/java/com/uniovi/services/impl/QuestionServiceImpl.java +++ b/src/main/java/com/uniovi/services/impl/QuestionServiceImpl.java @@ -3,6 +3,11 @@ import com.uniovi.entities.Question; import com.uniovi.repositories.QuestionRepository; import com.uniovi.services.QuestionService; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.querydsl.QPageRequest; import org.springframework.stereotype.Service; import java.sql.SQLException; @@ -21,10 +26,6 @@ public QuestionServiceImpl(QuestionRepository questionRepository) { @Override public void addNewQuestion(Question question) { - if (questionRepository.findByStatement(question.getStatement()) != null) { - return; - } - questionRepository.save(question); } @@ -40,4 +41,34 @@ public Optional getQuestion(Long id) { return questionRepository.findById(id); } + @Override + public Optional getRandomQuestion() { + List allQuestions = questionRepository.findAll().stream() + .filter(question -> question.getLanguage().equals(LocaleContextHolder.getLocale().getLanguage())).toList(); + int idx = (int) (Math.random() * allQuestions.size()); + Question q = allQuestions.get(idx); + return Optional.ofNullable(q); + } + + @Override + public List getRandomQuestions(int num) { + List allQuestions = questionRepository.findAll().stream() + .filter(question -> question.getLanguage().equals(LocaleContextHolder.getLocale().getLanguage())).toList(); + List res = new ArrayList<>(); + for (int i = 0; i < num; i++) { + int idx = (int) (Math.random() * allQuestions.size()); + res.add(allQuestions.get(idx)); + } + return res; + } + + @Override + public boolean checkAnswer(Long idquestion, Long idanswer) { + Optional q = questionRepository.findById(idquestion); + if (q.isPresent()) { + return q.get().getCorrectAnswer().getId().equals(idanswer); + } + return false; + } + } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 2c624ffb..a0ce684b 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -19,6 +19,7 @@ navbar.login=Inicia sesión # Buttons for authenticated users navbar.profile=Perfil navbar.logout=Cerrar sesión +navbar.profile.apikey=Clave de la API # -------------------Statements for the footer.html file--------------------- footer.copyright=© ASW - Grupo 04 B @@ -39,7 +40,10 @@ index.button=JUGAR home.heading=Bienvenido home.private_zone=Esta es una zona privada de la web home.authenticated_as=Usuario autenticado como: - +home.apikey.title=Clave de la API +home.apikey.description=Esta es tu clave de la API. Utilízala para acceder a los recursos de la API de WIQ. +home.apikey.missing=No tienes una clave de la API. Obtenla pulsando en este botón. +home.apikey.create=Obtener clave # -------------------Statements for the login.html file--------------------- login.username.label=Usuario: @@ -67,7 +71,10 @@ ranking.title=Ranking ranking.position=Posición ranking.score=Puntuación ranking.date=Fecha -ranking.player=Player +ranking.player=Jugador +ranking.time=Tiempo +ranking.question.right=Respuestas correctas +ranking.question.wrong=Respuestas incorrectas # -------------------Statements for the apiHome.html file--------------------- api.doc.title=Documentación de la API @@ -92,3 +99,12 @@ api.doc.player.emails=Correos electrónicos, separados por comas (opcional) api.doc.question.category=Categorña (opcional). Nombre o ID de la categoría. api.doc.question.id=ID de la pregunta (opcional) api.doc.question.statement=Enunciado de la pregunta (opcional). Texto que debe contener el enunciado de la pregunta. + +# -------------------Statements for the game fragments--------------------- +correctAnswer.result=¡Respuesta correcta, sigue así! +failedAnswer.result=Respuesta incorrecta, no te desanimes y sigue intentándolo. +timeRunOut.result=¡Se acabó el tiempo! No te preocupes, sigue intentándolo. +game.continue=Siguiente pregunta +answer.correct=La respuesta correcta era: +game.points=Puntos: +game.finish=El juego ha terminado. Tu puntuación ha sido: \ No newline at end of file diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties index e3c3842e..2f8bed6c 100644 --- a/src/main/resources/messages_en.properties +++ b/src/main/resources/messages_en.properties @@ -8,7 +8,6 @@ navbar.history=History navbar.ranking=Ranking navbar.ranking.global=Global ranking navbar.ranking.player=Your personal ranking -navbar.changeLanguage=Idiom navbar.changeLanguage=Language navbar.toEnglish=English navbar.toSpanish=Spanish @@ -20,6 +19,7 @@ navbar.login=Log In # Buttons for authenticated users navbar.profile=Profile navbar.logout=Log Out +navbar.profile.apikey=API Key # -------------------Statements for the footer.html file--------------------- footer.copyright=© ASW - Group 04 B @@ -40,6 +40,10 @@ index.button=PLAY home.heading=Welcome home.private_zone=This is a private zone of the website home.authenticated_as=Authenticated User as: +home.apikey.title=API Key +home.apikey.description=This is your api key. You can use it to access the WIQ's REST API. +home.apikey.missing=You don't have an API key yet. You can generate one by clicking the button below. +home.apikey.create=Obtain API Key # -------------------Statements for the login.html file--------------------- login.username.label=Username: @@ -91,4 +95,15 @@ api.doc.player.emails=Emails, comma separated (optional) api.doc.question.category=Category (optional). Category ID or name. api.doc.question.id=Question ID in the system (optional) -api.doc.question.statement=Statement (optional). Text to search in the question statement. \ No newline at end of file +api.doc.question.statement=Statement (optional). Text to search in the question statement. + +# -------------------Statements for the game fragments--------------------- +correctAnswer.result=¡Correct answer, keep it up! +failedAnswer.result=Incorrect answer, don't get discouraged and keep trying. +timeRunOut.result=Time's up! Don't worry, keep trying. +game.continue=Next question +answer.correct=Correct answer was: +game.points=Points: +game.finish=The game has finished. Your score is: + + diff --git a/src/main/resources/messages_es.properties b/src/main/resources/messages_es.properties index a3a71bd6..8ba5daaf 100644 --- a/src/main/resources/messages_es.properties +++ b/src/main/resources/messages_es.properties @@ -15,6 +15,7 @@ navbar.toSpanish=Español # Buttons for non-authenticated users navbar.signup=Regístrate navbar.login=Inicia sesión +navbar.profile.apikey=Clave de la API # Buttons for authenticated users navbar.profile=Perfil @@ -40,6 +41,10 @@ index.button=JUGAR home.heading=Bienvenido home.private_zone=Esta es una zona privada de la web home.authenticated_as=Usuario autenticado como: +home.apikey.title=Clave de la API +home.apikey.description=Esta es tu clave de la API. Utilízala para acceder a los recursos de la API de WIQ. +home.apikey.missing=No tienes una clave de la API. Obtenla pulsando en este botón. +home.apikey.create=Obtener clave # -------------------Statements for the login.html file--------------------- login.username.label=Usuario: @@ -91,4 +96,13 @@ api.doc.player.emails=Correos electrónicos, separados por comas (opcional) api.doc.question.category=Categorña (opcional). Nombre o ID de la categoría. api.doc.question.id=ID de la pregunta (opcional) -api.doc.question.statement=Enunciado de la pregunta (opcional). Texto que debe contener el enunciado de la pregunta. \ No newline at end of file +api.doc.question.statement=Enunciado de la pregunta (opcional). Texto que debe contener el enunciado de la pregunta. + +# -------------------Statements for the game fragments--------------------- +correctAnswer.result=¡Respuesta correcta, sigue así! +failedAnswer.result=Respuesta incorrecta, no te desanimes y sigue intentándolo. +timeRunOut.result=¡Se acabó el tiempo! No te preocupes, sigue intentándolo. +game.continue=Siguiente pregunta +answer.correct=La respuesta correcta era: +game.points=Puntos: +game.finish=El juego ha terminado. Tu puntuación ha sido: \ No newline at end of file diff --git a/src/main/resources/static/css/api.css b/src/main/resources/static/css/api.css index ecd2ad3a..77d80c4a 100644 --- a/src/main/resources/static/css/api.css +++ b/src/main/resources/static/css/api.css @@ -7,4 +7,12 @@ border-radius: 0.25rem; /* Rounded corners */ padding: 1rem; /* Padding inside the box */ border: 1px solid #d3d3d3; /* Light grey border */ +} + +h3 { + color: black; +} + +td { + color: white; } \ No newline at end of file diff --git a/src/main/resources/static/css/background.css b/src/main/resources/static/css/background.css index 0dbd1671..49270efe 100644 --- a/src/main/resources/static/css/background.css +++ b/src/main/resources/static/css/background.css @@ -1,7 +1,6 @@ html, body { margin: 0; height: 100%; - overflow: hidden; /* Evita barras de desplazamiento */ } #particle-canvas { diff --git a/src/main/resources/static/css/custom.css b/src/main/resources/static/css/custom.css index 60bb097e..0998067f 100644 --- a/src/main/resources/static/css/custom.css +++ b/src/main/resources/static/css/custom.css @@ -35,6 +35,10 @@ footer { } .table-hover { - color: #ffffff; + color: white; text-align:center; } + +.table-hover tbody tr:hover td { + background: white; +} diff --git a/src/main/resources/static/css/footer.css b/src/main/resources/static/css/footer.css index fad31081..77a89c6c 100644 --- a/src/main/resources/static/css/footer.css +++ b/src/main/resources/static/css/footer.css @@ -2,14 +2,13 @@ .footer { position: absolute; bottom: 0; - min-height: 75px; + min-height: 10%; width: 100%; - margin-top: 10%; /* Eliminar margen */ padding: 0; /* Eliminar relleno */ text-align: center; /* Alineación del texto */ display: flex; /* Usar flexbox para centrar verticalmente */ - align-items: center; /* Centrar verticalmente */ - + /*align-items: center; /* Centrar verticalmente */ + margin-top: 5%; background-color: transparent !important; /* Hace que el footer sea transparente */ } diff --git a/src/main/resources/static/css/game.css b/src/main/resources/static/css/game.css new file mode 100644 index 00000000..263cec64 --- /dev/null +++ b/src/main/resources/static/css/game.css @@ -0,0 +1,48 @@ +.stopwatch-container { + position: relative; + text-align: center; + width: 35%; + margin: auto; +} + +.countdown { + position: absolute; + top: 53%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 2em; + color: black; +} +.stopwatch-img { + width: 45%; + height: auto; +} + +.game-logo { + margin: 0 0 5% 0; + width: 25%; + height: auto; +} + +#bottomGamePart { + margin-bottom: 10%; +} + +.points { + font-size: 2em; + color: white; +} + +@media (max-width: 750px) { + .stopwatch-img { + width: 100%; + } + + h1, h2, h3 { + font-size: 1.5em; + } + + .countdown { + font-size: 3em; + } +} \ No newline at end of file diff --git a/src/main/resources/static/css/nav.css b/src/main/resources/static/css/nav.css index adbbf3bf..79c8ca3c 100644 --- a/src/main/resources/static/css/nav.css +++ b/src/main/resources/static/css/nav.css @@ -11,10 +11,6 @@ color: rgba(255, 255, 255, 0.7) !important; /* Cambia el color del texto del enlace cuando se pasa el mouse */ } -.navbar-toggler-icon { - background-color: white !important; /* Cambia el color del icono del toggler a blanco */ -} - .navbar-toggler { border-color: white !important; /* Cambia el color del borde del toggler a blanco */ } @@ -22,7 +18,7 @@ /* Estilo para los desplegables */ .dropdown-menu { color: #fff; - background-color: transparent; + background-color: black; border: 2px solid #fff; } diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css new file mode 100644 index 00000000..84d2796c --- /dev/null +++ b/src/main/resources/static/css/style.css @@ -0,0 +1,9 @@ +#apiKeyDiv { + display: flex; + flex-direction: column; + margin: 0 5% 5% 5%; +} + +#apiKeyDiv form { + margin: 5% 20% 0 20%; +} \ No newline at end of file diff --git a/src/main/resources/static/images/logo_correct.png b/src/main/resources/static/images/logo_correct.png new file mode 100644 index 00000000..a815be9f Binary files /dev/null and b/src/main/resources/static/images/logo_correct.png differ diff --git a/src/main/resources/static/images/logo_incorrect.png b/src/main/resources/static/images/logo_incorrect.png new file mode 100644 index 00000000..b8e10f7e Binary files /dev/null and b/src/main/resources/static/images/logo_incorrect.png differ diff --git a/src/main/resources/static/images/stopwatch.png b/src/main/resources/static/images/stopwatch.png new file mode 100644 index 00000000..b25b636c Binary files /dev/null and b/src/main/resources/static/images/stopwatch.png differ diff --git a/src/main/resources/static/script/background.js b/src/main/resources/static/script/background.js index 1aab4905..4be291bf 100644 --- a/src/main/resources/static/script/background.js +++ b/src/main/resources/static/script/background.js @@ -3,7 +3,7 @@ function normalPool(o){var r=0;do{var a=Math.round(normal({mean:o.mean,dev:o.dev const NUM_PARTICLES = 350; const PARTICLE_SIZE = 4; // View heights -const SPEED = 20000; // Milliseconds +const SPEED = 40000; // Milliseconds let particles = []; diff --git a/src/main/resources/static/script/show_hide_password.js b/src/main/resources/static/script/show_hide_password.js new file mode 100644 index 00000000..b14ec016 --- /dev/null +++ b/src/main/resources/static/script/show_hide_password.js @@ -0,0 +1,14 @@ +$(document).ready(function() { + $(".show_hide_password a").on('click', function(event) { + event.preventDefault(); + if($('.show_hide_password input').attr("type") == "text"){ + $('.show_hide_password input').attr('type', 'password'); + $('.show_hide_password i').addClass( "fa-eye-slash" ); + $('.show_hide_password i').removeClass( "fa-eye" ); + }else if($('.show_hide_password input').attr("type") == "password"){ + $('.show_hide_password input').attr('type', 'text'); + $('.show_hide_password i').removeClass( "fa-eye-slash" ); + $('.show_hide_password i').addClass( "fa-eye" ); + } + }); +}); \ No newline at end of file diff --git a/src/main/resources/templates/api/apiHome.html b/src/main/resources/templates/api/apiHome.html index c33769cb..942f3d9c 100644 --- a/src/main/resources/templates/api/apiHome.html +++ b/src/main/resources/templates/api/apiHome.html @@ -5,6 +5,8 @@ + +

diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html index 3a51fe76..66d23f12 100644 --- a/src/main/resources/templates/error.html +++ b/src/main/resources/templates/error.html @@ -4,8 +4,7 @@ - - +

diff --git a/src/main/resources/templates/fragments/background.html b/src/main/resources/templates/fragments/background.html new file mode 100644 index 00000000..a9e8fbfd --- /dev/null +++ b/src/main/resources/templates/fragments/background.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/templates/fragments/footer.html b/src/main/resources/templates/fragments/footer.html index 911b20a1..0579216d 100644 --- a/src/main/resources/templates/fragments/footer.html +++ b/src/main/resources/templates/fragments/footer.html @@ -13,7 +13,6 @@
-
diff --git a/src/main/resources/templates/fragments/head.html b/src/main/resources/templates/fragments/head.html index 1ee3832f..b75eb3c5 100644 --- a/src/main/resources/templates/fragments/head.html +++ b/src/main/resources/templates/fragments/head.html @@ -20,5 +20,6 @@ + diff --git a/src/main/resources/templates/fragments/nav.html b/src/main/resources/templates/fragments/nav.html index fb935b31..2a6bdc50 100644 --- a/src/main/resources/templates/fragments/nav.html +++ b/src/main/resources/templates/fragments/nav.html @@ -9,26 +9,25 @@