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": case sortOrder == "desc":
for { for {
splitPath.increment() splitPath.number = splitPath.increment()
path, err = tryExtensions(splitPath, formats) path, err = tryExtensions(splitPath, formats)
if err != nil { if err != nil {
@ -95,7 +95,7 @@ func newFile(list []string, sortOrder string, regexes *regexes, formats types.Ty
} }
if path == "" { if path == "" {
splitPath.decrement() splitPath.number = splitPath.decrement()
path, err = tryExtensions(splitPath, formats) path, err = tryExtensions(splitPath, formats)
if err != nil { if err != nil {
@ -119,9 +119,9 @@ func nextFile(filePath, sortOrder string, regexes *regexes, formats types.Types)
switch { switch {
case sortOrder == "asc": case sortOrder == "asc":
splitPath.increment() splitPath.number = splitPath.increment()
case sortOrder == "desc": case sortOrder == "desc":
splitPath.decrement() splitPath.number = splitPath.decrement()
default: default:
return "", nil 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 { func importIndex(args []string, index *fileIndex, formats types.Types) error {
if IndexFile != "" { if IndexFile != "" {
err := index.Import(IndexFile) err := index.Import(IndexFile)
if err != nil { if err == nil {
_, err := fileList(args, &filters{}, "", index, formats) return nil
if err != nil {
return err
}
} }
} }
_, err := fileList(args, &filters{}, "", index, formats)
if err != nil {
return err
}
return nil return nil
} }

View File

@ -19,7 +19,7 @@ import (
"seedno.de/seednode/roulette/types" "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 html strings.Builder
var firstPage int = 1 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(`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)) htmlBody.WriteString(fmt.Sprintf("<title>Index contains %d files</title></head><body><table>", fileCount))
if shouldPaginate { if shouldPaginate && !DisableButtons {
htmlBody.WriteString(paginate(page, fileCount, false)) htmlBody.WriteString(paginateIndex(page, fileCount, false))
} }
if len(indexDump) > 0 { if len(indexDump) > 0 {
@ -135,8 +135,8 @@ func serveIndexHtml(args []string, index *fileIndex, shouldPaginate bool) httpro
} }
} }
if shouldPaginate { if shouldPaginate && !DisableButtons {
htmlBody.WriteString(paginate(page, fileCount, true)) htmlBody.WriteString(paginateIndex(page, fileCount, true))
} }
htmlBody.WriteString(`</table></body></html>`) htmlBody.WriteString(`</table></body></html>`)

View File

@ -12,40 +12,41 @@ import (
) )
const ( const (
ReleaseVersion string = "3.0.2" ReleaseVersion string = "3.1.0"
) )
var ( var (
All bool All bool
Audio bool Audio bool
Bind string Bind string
CaseSensitive bool CaseSensitive bool
Code bool Code bool
CodeTheme string CodeTheme string
ExitOnError bool DisableButtons bool
Fallback bool ExitOnError bool
Filtering bool Fallback bool
Flash bool Filtering bool
Fun bool Flash bool
Handlers bool Fun bool
Images bool Handlers bool
Index bool Images bool
IndexFile string Index bool
Info bool IndexFile string
MaxFileCount int Info bool
MinFileCount int MaxFileCount int
PageLength int MinFileCount int
Port int PageLength int
Prefix string Port int
Profile bool Prefix string
Recursive bool Profile bool
Refresh bool Recursive bool
Russian bool Refresh bool
Sorting bool Russian bool
Text bool Sorting bool
Verbose bool Text bool
Version bool Verbose bool
Videos bool Version bool
Videos bool
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "roulette <path> [path]...", 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(&CaseSensitive, "case-sensitive", false, "use case-sensitive matching for filters")
rootCmd.Flags().BoolVar(&Code, "code", false, "enable support for source code files") 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().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(&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().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") rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering")

View File

@ -6,8 +6,12 @@ package cmd
import ( import (
"fmt" "fmt"
"sort"
"strings"
"strconv" "strconv"
"seedno.de/seednode/roulette/types"
) )
type splitPath struct { type splitPath struct {
@ -16,22 +20,22 @@ type splitPath struct {
extension string extension string
} }
func (splitPath *splitPath) increment() { func (splitPath *splitPath) increment() string {
asInt, err := strconv.Atoi(splitPath.number) asInt, err := strconv.Atoi(splitPath.number)
if err != nil { 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) asInt, err := strconv.Atoi(splitPath.number)
if err != nil { 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) { func split(path string, regexes *regexes) (*splitPath, error) {
@ -49,3 +53,126 @@ func split(path string, regexes *regexes) (*splitPath, error) {
return p, nil 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) refreshTimer, refreshInterval := refreshInterval(r)
rootUrl := Prefix + "/" + generateQueryParams(filters, sortOrder, refreshInterval) queryParams := generateQueryParams(filters, sortOrder, refreshInterval)
rootUrl := Prefix + "/" + queryParams
var htmlBody strings.Builder var htmlBody strings.Builder
htmlBody.WriteString(`<!DOCTYPE html><html class="bg" lang="en"><head>`) 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(title)
htmlBody.WriteString(`</head><body>`) 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" { if refreshInterval != "0ms" {
htmlBody.WriteString(refreshFunction(rootUrl, refreshTimer)) htmlBody.WriteString(refreshFunction(rootUrl, refreshTimer))
} }
@ -337,6 +366,7 @@ func serveMedia(paths []string, regexes *regexes, index *fileIndex, formats type
return return
} }
htmlBody.WriteString(body) htmlBody.WriteString(body)
htmlBody.WriteString(`</body></html>`) htmlBody.WriteString(`</body></html>`)
startTime := time.Now() 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 // enable image support if no other flags are passed, to retain backwards compatibility
// to be replaced with rootCmd.MarkFlagsOneRequired on next spf13/cobra update // to be replaced with rootCmd.MarkFlagsOneRequired on next spf13/cobra update
if Images || All || len(formats) == 0 { 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) paths, err := validatePaths(args, formats)

View File

@ -26,14 +26,21 @@ type dimensions struct {
} }
type Format struct { type Format struct {
Fun bool DisableButtons bool
Fun bool
} }
func (t Format) Css() string { func (t Format) Css() string {
var css strings.Builder var css strings.Builder
css.WriteString(`html,body{margin:0;padding:0;height:100%;}`) 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(`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%)`) css.WriteString(`object-fit:scale-down;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)`)
if t.Fun { if t.Fun {