Add first/prev/next/last buttons to sorted content

This commit is contained in:
Seednode 2023-10-20 17:18:35 -05:00
parent 9274162b4b
commit d902368845
7 changed files with 223 additions and 55 deletions

View File

@ -87,7 +87,7 @@ func newFile(list []string, sortOrder string, regexes *regexes, formats types.Ty
}
case sortOrder == "desc":
for {
splitPath.increment()
splitPath.number = splitPath.increment()
path, err = tryExtensions(splitPath, formats)
if err != nil {
@ -95,7 +95,7 @@ func newFile(list []string, sortOrder string, regexes *regexes, formats types.Ty
}
if path == "" {
splitPath.decrement()
splitPath.number = splitPath.decrement()
path, err = tryExtensions(splitPath, formats)
if err != nil {
@ -119,9 +119,9 @@ func nextFile(filePath, sortOrder string, regexes *regexes, formats types.Types)
switch {
case sortOrder == "asc":
splitPath.increment()
splitPath.number = splitPath.increment()
case sortOrder == "desc":
splitPath.decrement()
splitPath.number = splitPath.decrement()
default:
return "", nil
}

View File

@ -186,13 +186,15 @@ func registerIndexHandlers(mux *httprouter.Router, args []string, index *fileInd
func importIndex(args []string, index *fileIndex, formats types.Types) error {
if IndexFile != "" {
err := index.Import(IndexFile)
if err != nil {
_, err := fileList(args, &filters{}, "", index, formats)
if err != nil {
return err
}
if err == nil {
return nil
}
}
_, err := fileList(args, &filters{}, "", index, formats)
if err != nil {
return err
}
return nil
}

View File

@ -19,7 +19,7 @@ import (
"seedno.de/seednode/roulette/types"
)
func paginate(page int, fileCount int, ending bool) string {
func paginateIndex(page int, fileCount int, ending bool) string {
var html strings.Builder
var firstPage int = 1
@ -120,8 +120,8 @@ func serveIndexHtml(args []string, index *fileIndex, shouldPaginate bool) httpro
htmlBody.WriteString(`table,td,tr{border:none;}td{border-bottom:1px solid black;}td{white-space:nowrap;padding:.5em}</style>`)
htmlBody.WriteString(fmt.Sprintf("<title>Index contains %d files</title></head><body><table>", fileCount))
if shouldPaginate {
htmlBody.WriteString(paginate(page, fileCount, false))
if shouldPaginate && !DisableButtons {
htmlBody.WriteString(paginateIndex(page, fileCount, false))
}
if len(indexDump) > 0 {
@ -135,8 +135,8 @@ func serveIndexHtml(args []string, index *fileIndex, shouldPaginate bool) httpro
}
}
if shouldPaginate {
htmlBody.WriteString(paginate(page, fileCount, true))
if shouldPaginate && !DisableButtons {
htmlBody.WriteString(paginateIndex(page, fileCount, true))
}
htmlBody.WriteString(`</table></body></html>`)

View File

@ -12,40 +12,41 @@ import (
)
const (
ReleaseVersion string = "3.0.2"
ReleaseVersion string = "3.1.0"
)
var (
All bool
Audio bool
Bind string
CaseSensitive bool
Code bool
CodeTheme string
ExitOnError bool
Fallback bool
Filtering bool
Flash bool
Fun bool
Handlers bool
Images bool
Index bool
IndexFile string
Info bool
MaxFileCount int
MinFileCount int
PageLength int
Port int
Prefix string
Profile bool
Recursive bool
Refresh bool
Russian bool
Sorting bool
Text bool
Verbose bool
Version bool
Videos bool
All bool
Audio bool
Bind string
CaseSensitive bool
Code bool
CodeTheme string
DisableButtons bool
ExitOnError bool
Fallback bool
Filtering bool
Flash bool
Fun bool
Handlers bool
Images bool
Index bool
IndexFile string
Info bool
MaxFileCount int
MinFileCount int
PageLength int
Port int
Prefix string
Profile bool
Recursive bool
Refresh bool
Russian bool
Sorting bool
Text bool
Verbose bool
Version bool
Videos bool
rootCmd = &cobra.Command{
Use: "roulette <path> [path]...",
@ -88,6 +89,7 @@ func init() {
rootCmd.Flags().BoolVar(&CaseSensitive, "case-sensitive", false, "use case-sensitive matching for filters")
rootCmd.Flags().BoolVar(&Code, "code", false, "enable support for source code files")
rootCmd.Flags().StringVar(&CodeTheme, "code-theme", "solarized-dark256", "theme for source code syntax highlighting")
rootCmd.Flags().BoolVar(&DisableButtons, "disable-buttons", false, "disable first/prev/next/last buttons")
rootCmd.Flags().BoolVar(&ExitOnError, "exit-on-error", false, "shut down webserver on error, instead of just printing the error")
rootCmd.Flags().BoolVar(&Fallback, "fallback", false, "serve files as application/octet-stream if no matching format is registered")
rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering")

View File

@ -6,8 +6,12 @@ package cmd
import (
"fmt"
"sort"
"strings"
"strconv"
"seedno.de/seednode/roulette/types"
)
type splitPath struct {
@ -16,22 +20,22 @@ type splitPath struct {
extension string
}
func (splitPath *splitPath) increment() {
func (splitPath *splitPath) increment() string {
asInt, err := strconv.Atoi(splitPath.number)
if err != nil {
return
return ""
}
splitPath.number = fmt.Sprintf("%0*d", len(splitPath.number), asInt+1)
return fmt.Sprintf("%0*d", len(splitPath.number), asInt+1)
}
func (splitPath *splitPath) decrement() {
func (splitPath *splitPath) decrement() string {
asInt, err := strconv.Atoi(splitPath.number)
if err != nil {
return
return ""
}
splitPath.number = fmt.Sprintf("%0*d", len(splitPath.number), asInt-1)
return fmt.Sprintf("%0*d", len(splitPath.number), asInt-1)
}
func split(path string, regexes *regexes) (*splitPath, error) {
@ -49,3 +53,126 @@ func split(path string, regexes *regexes) (*splitPath, error) {
return p, nil
}
func getRange(path string, regexes *regexes, index *fileIndex) (string, string, error) {
splitPath, err := split(path, regexes)
if err != nil {
return "", "", err
}
list := index.List()
sort.Slice(list, func(i, j int) bool {
return list[i] <= list[j]
})
var first, last, previous string
Loop:
for _, val := range list {
splitVal, err := split(val, regexes)
if err != nil {
return "", "", err
}
switch {
case splitVal.base == splitPath.base && first == "":
first = val
case splitVal.base != splitPath.base && first != "":
last = previous
break Loop
}
previous = val
}
return first, last, nil
}
func paginateSorted(path, first, last, queryParams string, regexes *regexes, formats types.Types) (string, error) {
split, err := split(path, regexes)
if err != nil {
return "", err
}
var firstStatus, prevStatus, nextStatus, lastStatus string = "", "", "", ""
if path <= first {
firstStatus = " disabled"
prevStatus = " disabled"
}
if path >= last {
nextStatus = " disabled"
lastStatus = " disabled"
}
prevPath := &splitPath{
base: split.base,
number: split.decrement(),
extension: split.extension,
}
prevPage, err := tryExtensions(prevPath, formats)
switch {
case err != nil:
return "", err
case prevPage == "":
prevStatus = " disabled"
case prevPage < first:
prevPage = first
}
nextPath := &splitPath{
base: split.base,
number: split.increment(),
extension: split.extension,
}
nextPage, err := tryExtensions(nextPath, formats)
switch {
case err != nil:
return "", err
case nextPage == "":
nextStatus = " disabled"
case nextPage > last:
nextPage = last
}
var html strings.Builder
html.WriteString(`<table style="margin-left:auto;margin-right:auto;"><tr><td>`)
html.WriteString(fmt.Sprintf(`<button onclick="window.location.href = '%s%s%s%s';"%s>First</button>`,
Prefix,
mediaPrefix,
first,
queryParams,
firstStatus))
html.WriteString(fmt.Sprintf(`<button onclick="window.location.href = '%s%s%s%s';"%s>Prev</button>`,
Prefix,
mediaPrefix,
prevPage,
queryParams,
prevStatus))
html.WriteString(fmt.Sprintf(`<button onclick="window.location.href = '%s%s%s%s';"%s>Next</button>`,
Prefix,
mediaPrefix,
nextPage,
queryParams,
nextStatus))
html.WriteString(fmt.Sprintf(`<button onclick="window.location.href = '%s%s%s%s';"%s>Last</button>`,
Prefix,
mediaPrefix,
last,
queryParams,
lastStatus))
html.WriteString("</td></tr></table>")
return html.String(), nil
}

View File

@ -307,7 +307,9 @@ func serveMedia(paths []string, regexes *regexes, index *fileIndex, formats type
refreshTimer, refreshInterval := refreshInterval(r)
rootUrl := Prefix + "/" + generateQueryParams(filters, sortOrder, refreshInterval)
queryParams := generateQueryParams(filters, sortOrder, refreshInterval)
rootUrl := Prefix + "/" + queryParams
var htmlBody strings.Builder
htmlBody.WriteString(`<!DOCTYPE html><html class="bg" lang="en"><head>`)
@ -324,6 +326,33 @@ func serveMedia(paths []string, regexes *regexes, index *fileIndex, formats type
}
htmlBody.WriteString(title)
htmlBody.WriteString(`</head><body>`)
var first, last string
if Index && sortOrder != "" {
first, last, err = getRange(path, regexes, index)
if err != nil {
errorChannel <- err
serverError(w, r, nil)
return
}
}
if Index && !DisableButtons && sortOrder != "" {
paginate, err := paginateSorted(path, first, last, queryParams, regexes, formats)
if err != nil {
errorChannel <- err
serverError(w, r, nil)
return
}
htmlBody.WriteString(paginate)
}
if refreshInterval != "0ms" {
htmlBody.WriteString(refreshFunction(rootUrl, refreshTimer))
}
@ -337,6 +366,7 @@ func serveMedia(paths []string, regexes *regexes, index *fileIndex, formats type
return
}
htmlBody.WriteString(body)
htmlBody.WriteString(`</body></html>`)
startTime := time.Now()
@ -456,7 +486,7 @@ func ServePage(args []string) error {
// enable image support if no other flags are passed, to retain backwards compatibility
// to be replaced with rootCmd.MarkFlagsOneRequired on next spf13/cobra update
if Images || All || len(formats) == 0 {
formats.Add(images.Format{Fun: Fun})
formats.Add(images.Format{DisableButtons: DisableButtons, Fun: Fun})
}
paths, err := validatePaths(args, formats)

View File

@ -26,14 +26,21 @@ type dimensions struct {
}
type Format struct {
Fun bool
DisableButtons bool
Fun bool
}
func (t Format) Css() string {
var css strings.Builder
css.WriteString(`html,body{margin:0;padding:0;height:100%;}`)
css.WriteString(`a{color:inherit;display:block;height:100%;width:100%;text-decoration:none;}`)
if t.DisableButtons {
css.WriteString(`a{color:inherit;display:block;height:100%;width:100%;text-decoration:none;}`)
} else {
css.WriteString(`a{color:inherit;display:block;height:97%;width:100%;text-decoration:none;}`)
}
css.WriteString(`img{margin:auto;display:block;max-width:97%;max-height:97%;`)
css.WriteString(`object-fit:scale-down;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)`)
if t.Fun {