package ctfc import ( "strconv" "strings" "gitlab.com/Syndamia/ctfc/go-src/csi" "gitlab.com/Syndamia/ctfc/go-src/ui" ) type window func(...string) func StartupWindow(...string) { csi.ClearScreen() ui.NormalBox(true, "Login", "Register") input := ui.InputField("[L/R]") handled := handleInputActions(input, false, inputAction{"L", loginWindow, nil}, inputAction{"R", registerWindow, nil}, ) if !handled { defer showError(invalidCommand, StartupWindow) } } func loginWindow(values ...string) { // Takes form user input inputs := formWindow("Login", StartupWindow, []formInput{ {"Username", " or [B for Back to start page]", nil}, {"Password", "", nil}, }, values..., ) if len(inputs) == 0 { return } // If login is successful, go to chats window, else show error if logInUser(inputs[0], inputs[1]) { defer chatsWindow() } else { defer showError(invalidCredentials, loginWindow, inputs[0]) } } func registerWindow(values ...string) { inputs := formWindow("Register", StartupWindow, []formInput{ {"Username", " [A-z, 0-9, *, ., _, -; at least 1 letter] or [B for Back to start page]", stringValidUsername}, {"Password", " [5-40 characters; at least 1 number]", stringValidPassword}, {"Name", " [2-60 characters]", stringValidName}, }, values..., ) if len(inputs) == 0 { return } if usernameExists(inputs[0]) { defer showError(valueAlreadyTaken("Username"), registerWindow, values...) return } createUser(inputs) logInUser(inputs[0], inputs[1]) defer chatsWindow() } func chatsWindow(values ...string) { csi.ClearScreen() ui.NormalBox(true, "Direct Messages", "Account", "Logout") allChats := getAllChats() ui.NumberedFields(allChats...) ui.PageField(0, 0) ui.EmptyLine() if len(values) > 0 { if values[0] == "ShowHelp" { ui.TextField("Chats page options: [(D)irect messages/(A)ccount/(L)ogout/(<) for previous page/(>) for next page/(C) for create chat/(name) for go to chat room by name/(number) for go to chat room by number/(H)elp]") } } input := ui.InputField("[D/A/L//C/E/name/number/H]") handled := handleInputActions(input, true, inputAction{"C", createChatWindow, nil}, inputAction{"H", chatsWindow, []string{"ShowHelp"}}, ) if !handled { // If user input is number, navigate to chat of given number, else show error if chatI, err := strconv.Atoi(input); chatI >= 0 && chatI <= len(allChats) && err == nil { defer chatWindow(strings.Split(allChats[chatI-1], " : ")[0]) } else { defer showError(invalidCommand, chatsWindow) } } } func createChatWindow(values ...string) { inputs := formWindow("Creating a new chat", chatsWindow, []formInput{ {"Name", " [2-20 characters; A-z, 0-9, spaces, _, -] or [B for Back to chats page]", stringValidChatName}, {"Description", " [0-30 characters]", stringValidChatDesc}, }, values..., ) if len(inputs) == 0 { return } if chatNameExists(inputs[0]) { defer showError(valueAlreadyTaken("Chat name"), createChatWindow, values...) return } createChat(inputs[0], inputs[1], loggedInUser.Username) defer chatWindow(inputs[0]) } func chatWindow(values ...string) { csi.ClearScreen() // We determine page number by length of a string // This method should be faster than having to cast to int all the time if len(values) == 1 { values = append(values, ".") } currChat := getChat(values[0]) ui.NormalBox(true, "Chats", "Direct Messages", "Account", "Logout") ui.TextField(currChat.Name + " : " + currChat.Description) ui.TextField("Brought to you by " + currChat.Owner.Name + " (" + currChat.Owner.Username + ")") // One empty line to separate the "Brought to you by" line // with the chat messages, and then empty line for each chat message for i := 0; i <= pageSize+1; i++ { ui.EmptyLine() } lastLine := 0 go routinePaginatedSubwindow(&currChat.Messages, func(s *[]string) { *s = getChat(values[0]).Messages }, &lastLine, len(values[1]), ) input := ui.InputField("Message or [C/D/A/L//H]") handled := handleInputActions(input, true) if !handled { switch input { case ">": // If possible, increment to the next page (adds a dot to the end of the string) if len(values[1]) < totalPages(len(currChat.Messages)) { values[1] += "." } case "<": // If possible, decrement to the previous page (removes a dot from the string) if len(values[1]) > 1 { values[1] = values[1][:len(values[1])-1] } default: // Send a message values[1] = "." // Make sure to be at the first page, when sending a message // Since "C" is a command, to just enter the letter C you'll need to input "\C" currChat.addMessage(strings.TrimPrefix(input, "\\"), loggedInUser.Username) } defer chatWindow(values...) } lastLine = -1 // Practically stops execution of the paginated subwindow routine } func logoutWindow(...string) { csi.ClearScreen() ui.NormalBox(true, "Are you sure you want to Log out of "+loggedInUser.Username+"?") ui.EmptyLine() input := ui.InputField("[Y/N]") if strings.ToLower(input) == "y" { logoutUser() defer StartupWindow() } else { defer chatsWindow() } } func showError(message string, callback window, callbackData ...string) { csi.ClearScreen() ui.ErrorBox(message) csi.BlockClearScreenNextTime() defer callback(callbackData...) } /* Form */ type formInput struct { name string specification string validationFunc func(string) bool } func formWindow(boxTitle string, backWindow window, formInputs []formInput, currentValues ...string) (userInputs []string) { userInputs = currentValues // Go through all inputs for i, v := range formInputs[len(currentValues):] { csi.ClearScreen() ui.NormalBox(true, boxTitle) // Show filled input fields for j, va := range userInputs { ui.InputFieldFilled(formInputs[j].name, va) } // Wait for input on current input field input := ui.InputField(v.name + v.specification) // Go to the "backWindow", if we're on the first input, we are given a back window, and the letter B is given if i == 0 && backWindow != nil && strings.ToLower(input) == "b" { defer backWindow() return } // If we're validating input data, try to validate if v.validationFunc != nil { // If input data isn't valid, show an error and the same screen if !v.validationFunc(input) { formCallback := func(...string) { defer formWindow(boxTitle, backWindow, formInputs, userInputs...) } defer showError(invalidValueFor(v.name), formCallback) return } } // Add current input as a filled input userInputs = append(userInputs, input) } return } /* Input actions */ type inputAction struct { value string execute window args []string } func handleInputActions(input string, handleNav bool, ia ...inputAction) bool { if handleNav { ia = append(ia, inputAction{"C", chatsWindow, nil}, inputAction{"D", chatsWindow, nil}, inputAction{"A", chatsWindow, nil}, inputAction{"L", logoutWindow, nil}, ) } for _, v := range ia { if strings.ToLower(input) == strings.ToLower(v.value) { defer v.execute(v.args...) return true } } return false }