package ctfc import ( "strconv" "strings" "time" "gitlab.com/Syndamia/ctfc/go-src/csi" "gitlab.com/Syndamia/ctfc/go-src/ui" "gitlab.com/Syndamia/ctfc/go-src/utils" ) /* Pagination */ const pageSize = 15 func totalPages(messageAmount int) int { return utils.CeilDivInt(messageAmount, pageSize) } func paginate(page int, messages ...string) []string { return messages[utils.MaxInt(len(messages)-pageSize*page, 0) : len(messages)-pageSize*(page-1)] } // Must be run in a routine ( go routinePaginatedSubwindow(...) ) // There must be empty lines for the subwindow func routinePaginatedSubwindow(messages *[]string, updateMessages func(*[]string), lastLine *int, page int, customLinesBelow int, numbered bool) { for *lastLine > -2 { // Update messages, if we've already shown the last message if *lastLine == len(*messages)-1 { updateMessages(messages) continue } *lastLine = len(*messages) - 1 csi.SaveCursorPosition() csi.MoveCursorUpN(pageSize + 1 + customLinesBelow) csi.MoveCursorToBeginningOfLine() pageMessages := paginate(page, *messages...) // Leaves empty lines at the top, if there aren't enough messages to fill a full page // Works on the assumption that there are ui.EmptyLine(), where messages would be printed for i := 0; i < pageSize-len(pageMessages); i++ { csi.MoveCursorDown() } if numbered { ui.NumberedFields(pageMessages...) } else { ui.TextFields(pageMessages...) } ui.PageField(page, totalPages(len(*messages))) csi.RestoreCursorPosition() time.Sleep(500 * time.Millisecond) } } func initPaginatedValues(defaultLength int, values *[]string) { if len(*values) == defaultLength { *values = append(append(*values, "."), "") } } /* Input actions */ type inputAction struct { value string execute window args []string } func handleInputActions(input string, handleNav bool, ia ...inputAction) func() { if handleNav { ia = append(ia, inputAction{"C", chatsWindow, nil}, inputAction{"D", directMessagesWindow, nil}, inputAction{"A", accountWindow, nil}, inputAction{"L", logoutWindow, nil}, ) } for _, v := range ia { if strings.ToLower(input) == strings.ToLower(v.value) { return func() { v.execute(v.args...) } } } return nil } func handleComplexInputActions(input string, allValues []string, nameSep string, pageArgument *string, currentWindow window, currentWindowValues []string, singleValueWindow window, existsCheck func(string) bool) { // If user input is number, navigate to chat of given number, else show error if chatI, err := strconv.Atoi(input); chatI >= 0 && chatI <= len(allValues) && err == nil { defer singleValueWindow(strings.Split(allValues[utils.MaxInt(len(allValues)-pageSize*len(*pageArgument), 0)+chatI-1], " : ")[0]) } else if input == ">" { // If possible, increment to the next page (adds a dot to the end of the string) if len(*pageArgument) < totalPages(len(allValues)) { *pageArgument += "." } defer currentWindow(currentWindowValues...) } else if input == "<" { // If possible, decrement to the previous page (removes a dot from the string) if len(*pageArgument) > 1 { utils.StrShortenRight(*&pageArgument, 1) } defer currentWindow(currentWindowValues...) } else if existsCheck(input) { defer singleValueWindow(input) } else { defer showError(invalidCommand, currentWindow, currentWindowValues...) } } /* Form */ type formInput struct { name string specification string validationFunc func(string) bool } func formWindow(boxTitle string, backWindow window, formInputs []formInput, output *[]string) { // Go through all inputs for i, v := range formInputs[len(*output):] { csi.ClearScreen() ui.NormalBox(true, boxTitle) // Show filled input fields for j, va := range *output { 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, output) } defer showError(invalidValueFor(v.name), formCallback) return } } // Add current input as a filled input *output = append(*output, input) } return } type multiFormProp struct { propInd int editTitle string formInp formInput updateF func([]string) bool postSucUpdateF func([]string) } func validatedMultiForm(input string, returnWindow window, props ...multiFormProp) { propInd, _ := strconv.Atoi(input) inputs, triedUpdate, updateSuccessful := []formInput{{currentPasswordInName, inputBackSpec, nil}}, false, false for _, v := range props { if v.propInd == propInd { triedUpdate = true var values []string formWindow(v.editTitle, returnWindow, append(inputs, v.formInp), &values) // This is a huge bodge if !loggedInUser.ValidatePassword(values[0]) { defer showError(invalidValueFor(passwordInName), returnWindow) return } updateSuccessful = v.updateF(values) if updateSuccessful && v.postSucUpdateF != nil { v.postSucUpdateF(values) } break } } if !triedUpdate { defer showError(invalidCommand, returnWindow) } if !updateSuccessful { defer showError(invalidArgument, returnWindow) } else { defer returnWindow() } } /* Error */ func showError(message string, callback window, callbackData ...string) { csi.ClearScreen() ui.ErrorBox(message) csi.BlockClearScreenNextTime() defer callback(callbackData...) }