update settings and views (#41)

Co-authored-by: Graham Steffaniak <graham.steffaniak@autodesk.com>
This commit is contained in:
Graham Steffaniak 2023-10-09 17:24:48 -05:00 committed by GitHub
parent 5506135d32
commit a33eab2a8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 746 additions and 601 deletions

View File

@ -146,14 +146,11 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
} }
if u == nil { if u == nil {
pass, err := users.HashPwd(a.Cred.Password) log.Println("creds", a.Cred.Password)
if err != nil {
return nil, err
}
// create user with the provided credentials // create user with the provided credentials
d := &users.User{ d := &users.User{
Username: a.Cred.Username, Username: a.Cred.Username,
Password: pass, Password: a.Cred.Password,
Scope: a.Settings.UserDefaults.Scope, Scope: a.Settings.UserDefaults.Scope,
Locale: a.Settings.UserDefaults.Locale, Locale: a.Settings.UserDefaults.Locale,
ViewMode: a.Settings.UserDefaults.ViewMode, ViewMode: a.Settings.UserDefaults.ViewMode,
@ -178,16 +175,6 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
} }
} else if p := !users.CheckPwd(a.Cred.Password, u.Password); len(a.Fields.Values) > 1 || p { } else if p := !users.CheckPwd(a.Cred.Password, u.Password); len(a.Fields.Values) > 1 || p {
u = a.GetUser(u) u = a.GetUser(u)
// update the password when it doesn't match the current
if p {
pass, err := users.HashPwd(a.Cred.Password)
if err != nil {
return nil, err
}
u.Password = pass
}
// update user with provided fields // update user with provided fields
err := a.Users.Update(u) err := a.Users.Update(u)
if err != nil { if err != nil {

View File

@ -30,7 +30,6 @@ func (a JSONAuth) Auth(r *http.Request, usr users.Store) (*users.User, error) {
if r.Body == nil { if r.Body == nil {
return nil, os.ErrPermission return nil, os.ErrPermission
} }
err := json.NewDecoder(r.Body).Decode(&cred) err := json.NewDecoder(r.Body).Decode(&cred)
if err != nil { if err != nil {
return nil, os.ErrPermission return nil, os.ErrPermission

View File

@ -1,39 +0,0 @@
== Running benchmark ==
? github.com/gtsteffaniak/filebrowser [no test files]
? github.com/gtsteffaniak/filebrowser/auth [no test files]
? github.com/gtsteffaniak/filebrowser/cmd [no test files]
PASS
ok github.com/gtsteffaniak/filebrowser/diskcache 1.318s
? github.com/gtsteffaniak/filebrowser/errors [no test files]
? github.com/gtsteffaniak/filebrowser/files [no test files]
PASS
ok github.com/gtsteffaniak/filebrowser/fileutils 1.176s
2023/09/29 17:01:53 h: 401 <nil>
2023/09/29 17:01:53 h: 401 <nil>
2023/09/29 17:01:53 h: 401 <nil>
2023/09/29 17:01:53 h: 401 <nil>
2023/09/29 17:01:53 h: 401 <nil>
2023/09/29 17:01:53 h: 401 <nil>
PASS
ok github.com/gtsteffaniak/filebrowser/http 1.055s
PASS
ok github.com/gtsteffaniak/filebrowser/img 0.771s
goos: darwin
goarch: arm64
pkg: github.com/gtsteffaniak/filebrowser/index
BenchmarkFillIndex-10 10 6355542 ns/op 12084 B/op 449 allocs/op
PASS
ok github.com/gtsteffaniak/filebrowser/index 0.653s
PASS
ok github.com/gtsteffaniak/filebrowser/rules 0.549s
PASS
ok github.com/gtsteffaniak/filebrowser/runner 0.661s
PASS
ok github.com/gtsteffaniak/filebrowser/settings 0.665s
? github.com/gtsteffaniak/filebrowser/share [no test files]
? github.com/gtsteffaniak/filebrowser/storage [no test files]
? github.com/gtsteffaniak/filebrowser/storage/bolt [no test files]
PASS
ok github.com/gtsteffaniak/filebrowser/users 0.654s
? github.com/gtsteffaniak/filebrowser/version [no test files]

View File

@ -1,25 +0,0 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/gtsteffaniak/filebrowser/users"
)
func init() {
rootCmd.AddCommand(hashCmd)
}
var hashCmd = &cobra.Command{
Use: "hash <password>",
Short: "Hashes a password",
Long: `Hashes a password using bcrypt algorithm.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
pwd, err := users.HashPwd(args[0])
checkErr(err)
fmt.Println(pwd)
},
}

View File

@ -137,12 +137,23 @@ func quickSetup(d pythonData) {
log.Fatal("username and password cannot be empty during quick setup") log.Fatal("username and password cannot be empty during quick setup")
} }
user := &users.User{ user := &users.User{
Username: username, Username: username,
Password: password, Password: password,
LockPassword: false,
} }
settings.GlobalConfiguration.UserDefaults.Apply(user) settings.GlobalConfiguration.UserDefaults.Apply(user)
user.Perm.Admin = true user.Perm.Admin = true
user.DarkMode = true
user.ViewMode = "normal"
user.LockPassword = false
user.Perm = users.Permissions{
Create: true,
Rename: true,
Modify: true,
Delete: true,
Share: true,
Download: true,
Admin: true,
}
err = d.store.Users.Save(user) err = d.store.Users.Save(user)
checkErr(err) checkErr(err)
} }

View File

@ -16,12 +16,9 @@ var usersAddCmd = &cobra.Command{
Long: `Create a new user and add it to the database.`, Long: `Create a new user and add it to the database.`,
Args: cobra.ExactArgs(2), //nolint:gomnd Args: cobra.ExactArgs(2), //nolint:gomnd
Run: python(func(cmd *cobra.Command, args []string, d pythonData) { Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
password, err := users.HashPwd(args[1])
checkErr(err)
user := &users.User{ user := &users.User{
Username: args[0], Username: args[0],
Password: password, Password: args[1],
LockPassword: mustGetBool(cmd.Flags(), "lockPassword"), LockPassword: mustGetBool(cmd.Flags(), "lockPassword"),
} }
servSettings, err := d.store.Settings.GetServer() servSettings, err := d.store.Settings.GetServer()

View File

@ -3,9 +3,11 @@ server:
baseURL: "/" baseURL: "/"
root: "/Users/steffag/git/go" root: "/Users/steffag/git/go"
auth: auth:
method: noauth method: password
signup: true signup: true
userDefaults: userDefaults:
darkMode: true
disableSettings: false
scope: "." scope: "."
hideDotfiles: true hideDotfiles: true
singleClick: false singleClick: false
@ -16,6 +18,4 @@ userDefaults:
modify: true modify: true
delete: true delete: true
share: true share: true
download: true download: true
frontend:
theme: dark

View File

@ -12,6 +12,7 @@ import (
"github.com/golang-jwt/jwt/v4/request" "github.com/golang-jwt/jwt/v4/request"
"github.com/gtsteffaniak/filebrowser/errors" "github.com/gtsteffaniak/filebrowser/errors"
"github.com/gtsteffaniak/filebrowser/settings"
"github.com/gtsteffaniak/filebrowser/users" "github.com/gtsteffaniak/filebrowser/users"
) )
@ -19,20 +20,8 @@ const (
TokenExpirationTime = time.Hour * 2 TokenExpirationTime = time.Hour * 2
) )
type userInfo struct {
ID uint `json:"id"`
Locale string `json:"locale"`
ViewMode string `json:"viewMode"`
SingleClick bool `json:"singleClick"`
Perm users.Permissions `json:"perm"`
Commands []string `json:"commands"`
LockPassword bool `json:"lockPassword"`
HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"`
}
type authToken struct { type authToken struct {
User userInfo `json:"user"` User users.User `json:"user"`
jwt.RegisteredClaims jwt.RegisteredClaims
} }
@ -143,15 +132,9 @@ var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int,
user := &users.User{ user := &users.User{
Username: info.Username, Username: info.Username,
Password: info.Password,
} }
settings.GlobalConfiguration.UserDefaults.Apply(user)
pwd, err := users.HashPwd(info.Password)
if err != nil {
return http.StatusInternalServerError, err
}
user.Password = pwd
userHome, err := d.settings.MakeUserDir(user.Username, user.Scope, d.server.Root) userHome, err := d.settings.MakeUserDir(user.Username, user.Scope, d.server.Root)
if err != nil { if err != nil {
log.Printf("create user: failed to mkdir user home dir: [%s]", userHome) log.Printf("create user: failed to mkdir user home dir: [%s]", userHome)
@ -176,17 +159,7 @@ var renewHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data
func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User) (int, error) { func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User) (int, error) {
claims := &authToken{ claims := &authToken{
User: userInfo{ User: *user,
ID: user.ID,
Locale: user.Locale,
ViewMode: user.ViewMode,
SingleClick: user.SingleClick,
Perm: user.Perm,
LockPassword: user.LockPassword,
Commands: user.Commands,
HideDotfiles: user.HideDotfiles,
DateFormat: user.DateFormat,
},
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpirationTime)), ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpirationTime)),

View File

@ -9,7 +9,6 @@ import (
var searchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { var searchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
response := []map[string]interface{}{} response := []map[string]interface{}{}
query := r.URL.Query().Get("query") query := r.URL.Query().Get("query")
// Retrieve the User-Agent and X-Auth headers from the request // Retrieve the User-Agent and X-Auth headers from the request
sessionId := r.Header.Get("SessionId") sessionId := r.Header.Get("SessionId")
index := *index.GetIndex() index := *index.GetIndex()

View File

@ -129,7 +129,7 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
var token string var token string
if len(hash) > 0 { if len(hash) > 0 {
tokenBuffer := make([]byte, 96) //nolint:gomnd tokenBuffer := make([]byte, 24) //nolint:gomnd
if _, err := rand.Read(tokenBuffer); err != nil { if _, err := rand.Read(tokenBuffer); err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }

View File

@ -40,7 +40,6 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
"LoginPage": auther.LoginPage(), "LoginPage": auther.LoginPage(),
"CSS": false, "CSS": false,
"ReCaptcha": false, "ReCaptcha": false,
"Theme": d.settings.Frontend.Theme,
"EnableThumbs": d.server.EnableThumbnails, "EnableThumbs": d.server.EnableThumbnails,
"ResizePreview": d.server.ResizePreview, "ResizePreview": d.server.ResizePreview,
"EnableExec": d.server.EnableExec, "EnableExec": d.server.EnableExec,

View File

@ -124,11 +124,6 @@ var userPostHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *
return http.StatusBadRequest, errors.ErrEmptyPassword return http.StatusBadRequest, errors.ErrEmptyPassword
} }
req.Data.Password, err = users.HashPwd(req.Data.Password)
if err != nil {
return http.StatusInternalServerError, err
}
userHome, err := d.settings.MakeUserDir(req.Data.Username, req.Data.Scope, d.server.Root) userHome, err := d.settings.MakeUserDir(req.Data.Username, req.Data.Scope, d.server.Root)
if err != nil { if err != nil {
log.Printf("create user: failed to mkdir user home dir: [%s]", userHome) log.Printf("create user: failed to mkdir user home dir: [%s]", userHome)
@ -184,7 +179,6 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
if !d.user.Perm.Admin && d.user.LockPassword { if !d.user.Perm.Admin && d.user.LockPassword {
return http.StatusForbidden, nil return http.StatusForbidden, nil
} }
req.Data.Password, err = users.HashPwd(req.Data.Password) req.Data.Password, err = users.HashPwd(req.Data.Password)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err

View File

@ -1,6 +1,7 @@
package index package index
import ( import (
"mime"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -120,3 +121,35 @@ func updateSize(given string) int {
return size return size
} }
} }
func IsMatchingType(extension string, matchType string) bool {
mimetype := mime.TypeByExtension(extension)
if strings.HasPrefix(mimetype, matchType) {
return true
}
switch matchType {
case "doc":
return isDoc(extension)
case "archive":
return isArchive(extension)
}
return false
}
func isDoc(extension string) bool {
for _, typefile := range documentTypes {
if extension == typefile {
return true
}
}
return false
}
func isArchive(extension string) bool {
for _, typefile := range compressedFile {
if extension == typefile {
return true
}
}
return false
}

View File

@ -2,7 +2,6 @@ package index
import ( import (
"math/rand" "math/rand"
"mime"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
@ -61,10 +60,11 @@ func (si *Index) Search(search string, scope string, sourceSession string) ([]st
continue continue
} }
if isDir { if isDir {
pathName = pathName + "/" fileListTypes[pathName+"/"] = fileType
} else {
fileListTypes[pathName] = fileType
} }
matching = append(matching, pathName) matching = append(matching, pathName)
fileListTypes[pathName] = fileType
count++ count++
} }
} }
@ -88,39 +88,33 @@ func scopedPathNameFilter(pathName string, scope string) string {
return pathName return pathName
} }
var fileTypes = map[string]bool{
"audio": false,
"image": false,
"video": false,
"doc": false,
"archive": false,
"dir": false,
}
func containsSearchTerm(pathName string, searchTerm string, options SearchOptions, isDir bool) (bool, map[string]bool) { func containsSearchTerm(pathName string, searchTerm string, options SearchOptions, isDir bool) (bool, map[string]bool) {
conditions := options.Conditions conditions := options.Conditions
path := getLastPathComponent(pathName) path := getLastPathComponent(pathName)
// Convert to lowercase once // Convert to lowercase once
lowerSearchTerm := searchTerm
if !conditions["exact"] { if !conditions["exact"] {
path = strings.ToLower(path) path = strings.ToLower(path)
lowerSearchTerm = strings.ToLower(searchTerm) searchTerm = strings.ToLower(searchTerm)
} }
if strings.Contains(path, lowerSearchTerm) { if strings.Contains(path, searchTerm) {
// Reuse the fileTypes map and clear its values
fileTypes := map[string]bool{
"audio": false,
"image": false,
"video": false,
"doc": false,
"archive": false,
"dir": false,
}
// Calculate fileSize only if needed // Calculate fileSize only if needed
var fileSize int64 var fileSize int64
if conditions["larger"] || conditions["smaller"] {
fileSize = getFileSize(pathName)
}
matchesAllConditions := true matchesAllConditions := true
extension := filepath.Ext(path) extension := filepath.Ext(path)
mimetype := mime.TypeByExtension(extension) for k := range fileTypes {
fileTypes["audio"] = strings.HasPrefix(mimetype, "audio") fileTypes[k] = IsMatchingType(extension, k)
fileTypes["image"] = strings.HasPrefix(mimetype, "image") }
fileTypes["video"] = strings.HasPrefix(mimetype, "video")
fileTypes["doc"] = isDoc(extension)
fileTypes["archive"] = isArchive(extension)
fileTypes["dir"] = isDir fileTypes["dir"] = isDir
for t, v := range conditions { for t, v := range conditions {
if t == "exact" { if t == "exact" {
continue continue
@ -128,8 +122,14 @@ func containsSearchTerm(pathName string, searchTerm string, options SearchOption
var matchesCondition bool var matchesCondition bool
switch t { switch t {
case "larger": case "larger":
if fileSize == 0 {
fileSize = getFileSize(pathName)
}
matchesCondition = fileSize > int64(options.LargerThan)*bytesInMegabyte matchesCondition = fileSize > int64(options.LargerThan)*bytesInMegabyte
case "smaller": case "smaller":
if fileSize == 0 {
fileSize = getFileSize(pathName)
}
matchesCondition = fileSize < int64(options.SmallerThan)*bytesInMegabyte matchesCondition = fileSize < int64(options.SmallerThan)*bytesInMegabyte
default: default:
matchesCondition = v == fileTypes[t] matchesCondition = v == fileTypes[t]
@ -144,15 +144,6 @@ func containsSearchTerm(pathName string, searchTerm string, options SearchOption
return false, map[string]bool{} return false, map[string]bool{}
} }
func isDoc(extension string) bool {
for _, typefile := range documentTypes {
if extension == typefile {
return true
}
}
return false
}
func getFileSize(filepath string) int64 { func getFileSize(filepath string) int64 {
fileInfo, err := os.Stat(rootPath + "/" + filepath) fileInfo, err := os.Stat(rootPath + "/" + filepath)
if err != nil { if err != nil {
@ -161,15 +152,6 @@ func getFileSize(filepath string) int64 {
return fileInfo.Size() return fileInfo.Size()
} }
func isArchive(extension string) bool {
for _, typefile := range compressedFile {
if extension == typefile {
return true
}
}
return false
}
func getLastPathComponent(path string) string { func getLastPathComponent(path string) string {
// Use filepath.Base to extract the last component of the path // Use filepath.Base to extract the last component of the path
return filepath.Base(path) return filepath.Base(path)

View File

@ -61,14 +61,18 @@ func setDefaults() Settings {
}, },
Auth: Auth{ Auth: Auth{
Method: "password", Method: "password",
Signup: true,
Recaptcha: Recaptcha{ Recaptcha: Recaptcha{
Host: "", Host: "",
}, },
}, },
UserDefaults: UserDefaults{ UserDefaults: UserDefaults{
Scope: ".", Scope: ".",
LockPassword: false, LockPassword: false,
HideDotfiles: true, HideDotfiles: true,
DarkMode: false,
DisableSettings: false,
Locale: "en",
Permissions: users.Permissions{ Permissions: users.Permissions{
Create: true, Create: true,
Rename: true, Rename: true,
@ -76,6 +80,7 @@ func setDefaults() Settings {
Delete: true, Delete: true,
Share: true, Share: true,
Download: true, Download: true,
Admin: false,
}, },
}, },
} }
@ -83,6 +88,8 @@ func setDefaults() Settings {
// Apply applies the default options to a user. // Apply applies the default options to a user.
func (d *UserDefaults) Apply(u *users.User) { func (d *UserDefaults) Apply(u *users.User) {
u.DisableSettings = d.DisableSettings
u.DarkMode = d.DarkMode
u.Scope = d.Scope u.Scope = d.Scope
u.Locale = d.Locale u.Locale = d.Locale
u.ViewMode = d.ViewMode u.ViewMode = d.ViewMode

View File

@ -3,7 +3,6 @@ package settings
import ( import (
"github.com/gtsteffaniak/filebrowser/errors" "github.com/gtsteffaniak/filebrowser/errors"
"github.com/gtsteffaniak/filebrowser/rules" "github.com/gtsteffaniak/filebrowser/rules"
"github.com/gtsteffaniak/filebrowser/users"
) )
// StorageBackend is a settings storage backend. // StorageBackend is a settings storage backend.
@ -59,7 +58,7 @@ func (s *Storage) Save(set *Settings) error {
} }
if set.UserDefaults.ViewMode == "" { if set.UserDefaults.ViewMode == "" {
set.UserDefaults.ViewMode = users.MosaicViewMode set.UserDefaults.ViewMode = "normal"
} }
if set.Rules == nil { if set.Rules == nil {

View File

@ -61,13 +61,13 @@ type Frontend struct {
DisableExternal bool `json:"disableExternal"` DisableExternal bool `json:"disableExternal"`
DisableUsedPercentage bool `json:"disableUsedPercentage"` DisableUsedPercentage bool `json:"disableUsedPercentage"`
Files string `json:"files"` Files string `json:"files"`
Theme string `json:"theme"`
Color string `json:"color"` Color string `json:"color"`
} }
// UserDefaults is a type that holds the default values // UserDefaults is a type that holds the default values
// for some fields on User. // for some fields on User.
type UserDefaults struct { type UserDefaults struct {
DarkMode bool `json:"darkMode"`
LockPassword bool `json:"lockPassword"` LockPassword bool `json:"lockPassword"`
DisableSettings bool `json:"disableSettings,omitempty"` DisableSettings bool `json:"disableSettings,omitempty"`
Scope string `json:"scope"` Scope string `json:"scope"`

View File

@ -28,12 +28,13 @@ frontend:
disableExternal: false disableExternal: false
disableUsedPercentage: true disableUsedPercentage: true
files: "" files: ""
theme: ""
color: "" color: ""
userDefaults: userDefaults:
scope: "" scope: ""
locale: "" locale: ""
viewMode: "" viewMode: ""
darkMode: true
disableSettings: false
singleClick: true singleClick: true
sorting: sorting:
by: "" by: ""

View File

@ -2,6 +2,7 @@ package bolt
import ( import (
"fmt" "fmt"
"log"
"reflect" "reflect"
"github.com/asdine/storm/v3" "github.com/asdine/storm/v3"
@ -73,7 +74,13 @@ func (st usersBackend) Update(user *users.User, fields ...string) error {
} }
func (st usersBackend) Save(user *users.User) error { func (st usersBackend) Save(user *users.User) error {
err := st.db.Save(user) log.Println("userinfo", user.Password)
pass, err := users.HashPwd(user.Password)
if err != nil {
return err
}
user.Password = pass
err = st.db.Save(user)
if err == storm.ErrAlreadyExists { if err == storm.ErrAlreadyExists {
return errors.ErrExist return errors.ErrExist
} }

View File

@ -1,11 +1,14 @@
package users package users
import ( import (
"log"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
// HashPwd hashes a password. // HashPwd hashes a password.
func HashPwd(password string) (string, error) { func HashPwd(password string) (string, error) {
log.Println("hashing password", password)
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err return string(bytes), err
} }

View File

@ -73,7 +73,7 @@ func (s *Storage) Gets(baseScope string) ([]*User, error) {
// Update updates a user in the database. // Update updates a user in the database.
func (s *Storage) Update(user *User, fields ...string) error { func (s *Storage) Update(user *User, fields ...string) error {
err := user.Clean("", fields...) err := user.Clean("")
if err != nil { if err != nil {
return err return err
} }

View File

@ -6,16 +6,10 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/gtsteffaniak/filebrowser/errors"
"github.com/gtsteffaniak/filebrowser/files" "github.com/gtsteffaniak/filebrowser/files"
"github.com/gtsteffaniak/filebrowser/rules" "github.com/gtsteffaniak/filebrowser/rules"
) )
var (
ListViewMode = "list"
MosaicViewMode = "mosaic"
)
type Permissions struct { type Permissions struct {
Admin bool `json:"admin"` Admin bool `json:"admin"`
Execute bool `json:"execute"` Execute bool `json:"execute"`
@ -29,21 +23,23 @@ type Permissions struct {
// User describes a user. // User describes a user.
type User struct { type User struct {
ID uint `storm:"id,increment" json:"id"` DarkMode bool `json:"darkMode"`
Username string `storm:"unique" json:"username"` DisableSettings bool `json:"disableSettings"`
Password string `json:"password"` ID uint `storm:"id,increment" json:"id"`
Scope string `json:"scope"` Username string `storm:"unique" json:"username"`
Locale string `json:"locale"` Password string `json:"password"`
LockPassword bool `json:"lockPassword"` Scope string `json:"scope"`
ViewMode string `json:"viewMode"` Locale string `json:"locale"`
SingleClick bool `json:"singleClick"` LockPassword bool `json:"lockPassword"`
Perm Permissions `json:"perm"` ViewMode string `json:"viewMode"`
Commands []string `json:"commands"` SingleClick bool `json:"singleClick"`
Sorting files.Sorting `json:"sorting"` Perm Permissions `json:"perm"`
Fs afero.Fs `json:"-" yaml:"-"` Commands []string `json:"commands"`
Rules []rules.Rule `json:"rules"` Sorting files.Sorting `json:"sorting"`
HideDotfiles bool `json:"hideDotfiles"` Fs afero.Fs `json:"-" yaml:"-"`
DateFormat bool `json:"dateFormat"` Rules []rules.Rule `json:"rules"`
HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"`
} }
// GetRules implements rules.Provider. // GetRules implements rules.Provider.
@ -51,53 +47,11 @@ func (u *User) GetRules() []rules.Rule {
return u.Rules return u.Rules
} }
var checkableFields = []string{
"Username",
"Password",
"Scope",
"ViewMode",
"Commands",
"Sorting",
"Rules",
}
// Clean cleans up a user and verifies if all its fields // Clean cleans up a user and verifies if all its fields
// are alright to be saved. // are alright to be saved.
// //
//nolint:gocyclo //nolint:gocyclo
func (u *User) Clean(baseScope string, fields ...string) error { func (u *User) Clean(baseScope string) error {
if len(fields) == 0 {
fields = checkableFields
}
for _, field := range fields {
switch field {
case "Username":
if u.Username == "" {
return errors.ErrEmptyUsername
}
case "Password":
if u.Password == "" {
return errors.ErrEmptyPassword
}
case "ViewMode":
if u.ViewMode == "" {
u.ViewMode = ListViewMode
}
case "Commands":
if u.Commands == nil {
u.Commands = []string{}
}
case "Sorting":
if u.Sorting.By == "" {
u.Sorting.By = "name"
}
case "Rules":
if u.Rules == nil {
u.Rules = []rules.Rule{}
}
}
}
if u.Fs == nil { if u.Fs == nil {
scope := u.Scope scope := u.Scope

View File

@ -2,7 +2,7 @@ package version
var ( var (
// Version is the current File Browser version. // Version is the current File Browser version.
Version = "(0.2.0)" Version = "(0.2.1)"
// CommitSHA is the commmit sha. // CommitSHA is the commmit sha.
CommitSHA = "(unknown)" CommitSHA = "(unknown)"
) )

View File

@ -40,6 +40,8 @@ frontend:
theme: "" theme: ""
color: "" color: ""
userDefaults: userDefaults:
settingsAllowed: true
darkMode: false
scope: "" scope: ""
locale: "" locale: ""
viewMode: "" viewMode: ""
@ -176,6 +178,10 @@ UserDefaults:
### UserDefaults configuration settings ### UserDefaults configuration settings
- `darkMode`: Determines whether dark mode is enabled for the user (true or false)
- `settingsAllowed`: Determines whether settings page is enabled for the user (true or false)
- `scope`: This is a scope of the permissions, "." or "./" means all directories, "./downloads" would mean only the downloads folder. - `scope`: This is a scope of the permissions, "." or "./" means all directories, "./downloads" would mean only the downloads folder.
- `locale`: This is the locale configuration. - `locale`: This is the locale configuration.

View File

@ -35,7 +35,7 @@
"devDependencies": { "devDependencies": {
"@vue/cli-service": "^5.0.8", "@vue/cli-service": "^5.0.8",
"compression-webpack-plugin": "^10.0.0", "compression-webpack-plugin": "^10.0.0",
"eslint": "^8.50.0", "eslint": "^8.51.0",
"eslint-plugin-vue": "^9.17.0", "eslint-plugin-vue": "^9.17.0",
"vue-template-compiler": "^2.6.10" "vue-template-compiler": "^2.6.10"
} }
@ -293,9 +293,9 @@
} }
}, },
"node_modules/@eslint-community/regexpp": { "node_modules/@eslint-community/regexpp": {
"version": "4.8.1", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz",
"integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0" "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
@ -347,9 +347,9 @@
"dev": true "dev": true
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "8.50.0", "version": "8.51.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz",
"integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -3025,15 +3025,15 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.50.0", "version": "8.51.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz",
"integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1", "@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.2", "@eslint/eslintrc": "^2.1.2",
"@eslint/js": "8.50.0", "@eslint/js": "8.51.0",
"@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/config-array": "^0.11.11",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
@ -3774,12 +3774,12 @@
} }
}, },
"node_modules/flat-cache": { "node_modules/flat-cache": {
"version": "3.1.0", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz",
"integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"flatted": "^3.2.7", "flatted": "^3.2.9",
"keyv": "^4.5.3", "keyv": "^4.5.3",
"rimraf": "^3.0.2" "rimraf": "^3.0.2"
}, },
@ -3970,9 +3970,9 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
}, },
"node_modules/globals": { "node_modules/globals": {
"version": "13.22.0", "version": "13.23.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
"integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"type-fest": "^0.20.2" "type-fest": "^0.20.2"
@ -4747,9 +4747,9 @@
} }
}, },
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.3", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"json-buffer": "3.0.1" "json-buffer": "3.0.1"
@ -7974,9 +7974,9 @@
} }
}, },
"node_modules/vue-eslint-parser": { "node_modules/vue-eslint-parser": {
"version": "9.3.1", "version": "9.3.2",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.2.tgz",
"integrity": "sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==", "integrity": "sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"debug": "^4.3.4", "debug": "^4.3.4",

View File

@ -39,7 +39,7 @@
"devDependencies": { "devDependencies": {
"@vue/cli-service": "^5.0.8", "@vue/cli-service": "^5.0.8",
"compression-webpack-plugin": "^10.0.0", "compression-webpack-plugin": "^10.0.0",
"eslint": "^8.50.0", "eslint": "^8.51.0",
"eslint-plugin-vue": "^9.17.0", "eslint-plugin-vue": "^9.17.0",
"vue-template-compiler": "^2.6.10" "vue-template-compiler": "^2.6.10"
}, },

View File

@ -128,8 +128,8 @@
</div> </div>
</div> </div>
[{[ if .Theme -]}] [{[ if .darkMode -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css" /> <link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/dark.css" />
[{[ end ]}] [{[ end ]}]
[{[ if .CSS -]}] [{[ if .CSS -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" /> <link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />

View File

@ -1,220 +0,0 @@
:root {
--background: #141D24;
--surfacePrimary: #20292F;
--surfaceSecondary: #3A4147;
--divider: rgba(255, 255, 255, 0.12);
--textPrimary: rgba(255, 255, 255, 0.87);
--textSecondary: rgba(255, 255, 255, 0.6);
}
body {
background: var(--background);
color: var(--textPrimary);
}
#loading {
background: var(--background);
}
#login {
background: var(--background);
}
header {
background: var(--surfacePrimary);
}
@supports (backdrop-filter: none) {
header {
background: transparent;
backdrop-filter: blur(16px) invert(0.1);
}
}
#search #input {
background: var(--surfaceSecondary);
border-color: var(--surfaceSecondary);
}
#search #input input::placeholder {
color: var(--textSecondary);
}
#search.active #input {
background: var(--surfacePrimary);
border-color: white;
}
#search.active input {
color: var(--textPrimary);
}
#search #result {
background: var(--background);
color: var(--textPrimary);
}
#search .boxes h3 {
color: var(--textPrimary);
}
.action {
color: var(--textPrimary) !important;
}
.action:hover {
background-color: rgba(255, 255, 255, .1);
}
.action .counter {
border-color: var(--surfacePrimary);
}
nav > div {
border-color: var(--divider);
}
.breadcrumbs {
border-color: var(--divider);
color: var(--textPrimary) !important;
}
.breadcrumbs span {
color: var(--textPrimary) !important;
}
.breadcrumbs a:hover {
background-color: rgba(255, 255, 255, .1);
}
#listing .item {
background: var(--surfacePrimary);
color: var(--textPrimary);
border-color: var(--divider) !important;
}
#listing .item .modified {
color: var(--textSecondary);
}
#listing h2,
#listing.list .header span {
color: var(--textPrimary) !important;
}
#listing.list .header span {
color: var(--textPrimary);
}
#listing.list .item.header {
background: var(--background);
}
.message {
color: var(--textPrimary);
}
.card {
background: var(--surfacePrimary);
color: var(--textPrimary);
}
.button--flat:hover {
background: var(--surfaceSecondary);
}
.dashboard #nav ul li {
color: var(--textSecondary);
}
.dashboard #nav ul li:hover {
background: var(--surfaceSecondary);
}
#result-list {
background-color:#292929;
}
.card h3,
.dashboard #nav,
.dashboard p label {
color: var(--textPrimary);
}
.card#share input,
.card#share select,
.input {
background: var(--surfaceSecondary);
color: var(--textPrimary);
}
.input:hover,
.input:focus {
border-color: rgba(255, 255, 255, 0.15);
}
.input--red {
background: #73302D;
}
.input--green {
background: #147A41;
}
.dashboard #nav .wrapper,
.collapsible {
border-color: var(--divider);
}
.collapsible > label * {
color: var(--textPrimary);
}
table th {
color: var(--textSecondary);
}
.file-list li:hover {
background: var(--surfaceSecondary);
}
.file-list li:before {
color: var(--textSecondary);
}
.shell {
background: var(--surfacePrimary);
color: var(--textPrimary);
}
.shell__result {
border-top: 1px solid var(--divider);
}
#editor-container {
background: var(--background);
}
#editor-container .bar {
background: var(--surfacePrimary);
}
nav {
background: var(--surfaceSecondary) !important;
}
#file-selection {
background: var(--surfaceSecondary) !important;
}
#file-selection span {
color: var(--textPrimary) !important;
}
#dropdown {
background: var(--surfaceSecondary) !important;
}
.share__box {
background: var(--surfacePrimary) !important;
color: var(--textPrimary);
}
.share__box__element {
border-top-color: var(--divider);
}
.helpButton {
background: var(--background);
}
.sizeInputWrapper {
background: var(--background);
color: white
}
.button-group button {
background: var(--background);
color: white
}
#result-desktop #result-list {
background: #2a3137;
max-height: unset;
}

View File

@ -1,18 +1,20 @@
<template> <template>
<router-view></router-view> <router-view></router-view>
</template> </template>
<script> <script>
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
__webpack_public_path__ = window.FileBrowser.StaticURL + "/"; __webpack_public_path__ = window.FileBrowser.StaticURL + "/";
export default { export default {
name: "app", name: "app",
computed: {
},
mounted() { mounted() {
const loading = document.getElementById("loading"); const loading = document.getElementById("loading");
loading.classList.add("done"); loading.classList.add("done");
setTimeout(() => {
setTimeout(function () {
loading.parentNode.removeChild(loading); loading.parentNode.removeChild(loading);
}, 200); }, 200);
}, },
@ -20,5 +22,7 @@ export default {
</script> </script>
<style> <style>
/* Always load styles.css */
@import "./css/styles.css"; @import "./css/styles.css";
@import "./css/dark.css";
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div id="search" @click="open" v-bind:class="{ active, ongoing }"> <div id="search" @click="open" v-bind:class="{ active, ongoing, 'dark-mode': isDarkMode }">
<div id="input"> <div id="input">
<button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')" :title="$t('buttons.close')"> <button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')" :title="$t('buttons.close')">
<i class="material-icons">close</i> <i class="material-icons">close</i>
@ -149,6 +149,7 @@
padding-bottom: 1em; padding-bottom: 1em;
-webkit-transition: width 0.3s ease 0s; -webkit-transition: width 0.3s ease 0s;
transition: width 0.3s ease 0s; transition: width 0.3s ease 0s;
background-color: unset;
} }
#result-desktop { #result-desktop {
@ -172,6 +173,8 @@
background-color: lightgray; background-color: lightgray;
max-height: 80vh; max-height: 80vh;
overflow: hidden; overflow: hidden;
display: flex;
flex-direction: column;
} }
#search.active #result-desktop ul li a { #search.active #result-desktop ul li a {
@ -201,6 +204,7 @@
/* Search */ /* Search */
#search { #search {
background-color:unset;
z-index:3; z-index:3;
position: fixed; position: fixed;
top: .5em; top: .5em;
@ -314,7 +318,7 @@ body.rtl #search #result ul>* {
} }
#search.active #input { #search.active #input {
background-color: lightgray; background-color: var(--background);
border-color: black; border-color: black;
border-style: solid; border-style: solid;
border-bottom-style: none; border-bottom-style: none;
@ -485,7 +489,7 @@ export default {
{ label: "Photos", value: "type:image" }, { label: "Photos", value: "type:image" },
{ label: "Audio", value: "type:audio" }, { label: "Audio", value: "type:audio" },
{ label: "Videos", value: "type:video" }, { label: "Videos", value: "type:video" },
{ label: "Documents", value: "type:docs" }, { label: "Documents", value: "type:doc" },
{ label: "Archives", value: "type:archive" }, { label: "Archives", value: "type:archive" },
], ],
value: "", value: "",
@ -538,6 +542,9 @@ export default {
computed: { computed: {
...mapState(["user", "show"]), ...mapState(["user", "show"]),
...mapGetters(["isListing"]), ...mapGetters(["isListing"]),
isDarkMode() {
return this.user.darkMode === true
},
showBoxes() { showBoxes() {
return this.searchTypes == ""; return this.searchTypes == "";
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<nav :class="{ active }"> <nav :class="{ active, 'dark-mode': isDarkMode }">
<template v-if="isLogged"> <template v-if="isLogged">
<button class="action" @click="toRoot" :aria-label="$t('sidebar.myFiles')" :title="$t('sidebar.myFiles')"> <button class="action" @click="toRoot" :aria-label="$t('sidebar.myFiles')" :title="$t('sidebar.myFiles')">
<i class="material-icons">folder</i> <i class="material-icons">folder</i>
@ -87,6 +87,9 @@ export default {
}, },
computed: { computed: {
...mapState(["user"]), ...mapState(["user"]),
isDarkMode() {
return this.user.darkMode === true
},
...mapGetters(["isLogged"]), ...mapGetters(["isLogged"]),
active() { active() {
return this.$store.state.show === "sidebar"; return this.$store.state.show === "sidebar";

View File

@ -0,0 +1,24 @@
<template>
<select v-on:change="change" :value="viewMode">
<option v-for="mode in viewModes" :key="mode" :value="mode">
{{ mode }}
</option>
</select>
</template>
<script>
export default {
name: "ViewMode",
props: ["viewMode"],
data() {
return {
viewModes: ['list', 'compact', 'normal', 'gallery'],
};
},
methods: {
change(event) {
this.$emit("update:viewMode", event.target.value);
},
},
};
</script>

View File

@ -203,6 +203,7 @@ body.rtl .breadcrumbs a {
width: 95%; width: 95%;
max-width: 30em; max-width: 30em;
z-index: 1; z-index: 1;
border-radius: 1em;
} }
button { button {

292
frontend/src/css/dark.css Normal file
View File

@ -0,0 +1,292 @@
/* Define a class .dark-mode for dark mode styles */
.dark-mode {
--background: #141D24;
--surfacePrimary: #20292F;
--surfaceSecondary: #3A4147;
--divider: rgba(255, 255, 255, 0.12);
--textPrimary: rgba(255, 255, 255, 0.87);
--textSecondary: rgba(255, 255, 255, 0.6);
}
.dark-mode #loading {
background: var(--background);
}
.dark-mode #login {
background: var(--background);
}
/* Loading */
.dark-mode #loading {
background: var(--background);
}
/* Login */
.dark-mode #login {
background: var(--background);
}
/* Header */
.dark-mode header {
background: var(--surfacePrimary);
}
/* Header with backdrop-filter support */
@supports (backdrop-filter: none) {
.dark-mode header {
background: transparent;
backdrop-filter: blur(16px) invert(0.1);
}
}
#search.dark-mode input {
color:white
}
#search.active.dark-mode #input {
border-color: white;
}
/* Search input */
.dark-mode #search #input {
background: var(--surfaceSecondary);
border-color: var(--surfaceSecondary);
}
.dark-mode #search #input input::placeholder {
color: var(--textSecondary);
}
/* Active Search input */
.dark-mode #search.active #input {
background: var(--surfacePrimary);
border-color: white;
}
.dark-mode #search.active input {
color: var(--textPrimary);
}
/* Search result */
.dark-mode #search #result {
background: var(--background);
color: var(--textPrimary);
}
/* Search boxes */
.dark-mode #search .boxes h3 {
color: var(--textPrimary);
}
/* Action */
.dark-mode .action {
color: var(--textPrimary) !important;
}
.dark-mode .action:hover {
background-color: rgba(255, 255, 255, .1);
}
/* Action counter */
.dark-mode .action .counter {
border-color: var(--surfacePrimary);
}
/* Navigation */
.dark-mode nav > div {
border-color: var(--divider);
}
/* Breadcrumbs */
.dark-mode .breadcrumbs {
border-color: var(--divider);
color: var(--textPrimary) !important;
}
.dark-mode .breadcrumbs span {
color: var(--textPrimary) !important;
}
.dark-mode .breadcrumbs a:hover {
background-color: rgba(255, 255, 255, .1);
}
/* Listing items */
.dark-mode #listing .item {
background: var(--surfacePrimary);
color: var(--textPrimary);
border-color: var(--divider) !important;
}
/* Listing item modified text */
.dark-mode #listing .item .modified {
color: var(--textSecondary);
}
/* Listing header and span */
.dark-mode #listing h2,
.dark-mode #listing.list .header span {
color: var(--textPrimary) !important;
}
/* Message */
.dark-mode .message {
color: var(--textPrimary);
}
/* Card */
.dark-mode .card {
background: var(--surfacePrimary);
color: var(--textPrimary);
}
/* Flat button hover */
.dark-mode .button--flat:hover {
background: var(--surfaceSecondary);
}
/* Dashboard navigation */
.dark-mode .dashboard #nav ul li {
color: var(--textSecondary);
}
.dark-mode .dashboard #nav ul li:hover {
background: var(--surfaceSecondary);
}
#search.active.dark-mode #result {
background-color: black;
}
/* Result list */
.dark-mode #result-list {
background-color: var(--surfacePrimary);
color:white;
}
/* Card, Dashboard navigation, and label */
.dark-mode .card h3,
.dark-mode .dashboard #nav,
.dark-mode .dashboard p label {
color: var(--textPrimary);
}
.dark-mode .card#share input,
.dark-mode .card#share select,
.dark-mode .input {
background: var(--surfaceSecondary);
color: var(--textPrimary);
}
/* Input hover and focus */
.dark-mode .input:hover,
.dark-mode .input:focus {
border-color: rgba(255, 255, 255, 0.15);
}
/* Red input */
.dark-mode .input--red {
background: #73302D;
}
/* Green input */
.dark-mode .input--green {
background: #147A41;
}
/* Collapsible and label */
.dark-mode .dashboard #nav .wrapper,
.dark-mode .collapsible {
border-color: var(--divider);
}
.dark-mode .collapsible > label * {
color: var(--textPrimary);
}
/* Table header */
.dark-mode table th {
color: var(--textSecondary);
}
/* File list item */
.dark-mode .file-list li:hover {
background: var(--surfaceSecondary);
}
.dark-mode .file-list li:before {
color: var(--textSecondary);
}
/* Shell */
.dark-mode .shell {
background: var(--surfacePrimary);
color: var(--textPrimary);
}
/* Shell result */
.dark-mode .shell__result {
border-top: 1px solid var(--divider);
}
/* Editor container */
.dark-mode #editor-container {
background: var(--background);
}
.dark-mode #editor-container .bar {
background: var(--surfacePrimary);
}
/* Navigation */
.dark-mode nav {
background: var(--surfaceSecondary) !important;
}
/* File selection */
.dark-mode #file-selection {
background: var(--surfaceSecondary) !important;
}
.dark-mode #file-selection span {
color: var(--textPrimary) !important;
}
/* Dropdown */
.dark-mode #dropdown {
background: var(--surfaceSecondary) !important;
}
/* Share box */
.dark-mode .share__box {
background: var(--surfacePrimary) !important;
color: var(--textPrimary);
}
/* Share box element */
.dark-mode .share__box__element {
border-top-color: var(--divider);
}
/* Help button */
.dark-mode .helpButton {
background: var(--background);
}
/* Size input wrapper */
.dark-mode .sizeInputWrapper {
background: var(--background);
color: white;
}
/* Button group button */
.dark-mode .button-group button {
background: var(--background);
color: white;
}
/* Result desktop */
.dark-mode #result-desktop #result-list {
max-height: unset;
}
/* Result desktop background */
.dark-mode #result-desktop {
background-color: var(--background);
}

View File

@ -85,41 +85,53 @@ body.rtl #listing {
display: block; display: block;
} }
#listing.mosaic { #listing {
padding-top: 1em; padding-top: 1em;
margin: 0 -0.5em; margin: 0 -0.5em;
} }
#listing.mosaic .item { #listing.gallery .item,
#listing.compact .item,
#listing.normal .item,
#listing.list .item {
width: calc(33% - 1em); width: calc(33% - 1em);
max-width: 300px;
margin: .5em; margin: .5em;
padding: 0.5em; padding: 0.5em;
border-radius: 1em; border-radius: 1em;
box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12); box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12);
} }
#listing.gallery .item {
max-width: 300px;
}
#listing.list .item,
#listing.compact .item {
max-width: 100%;
border-radius: 0em;
}
#listing.mosaic .item:hover { #listing .item:hover {
box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !important; box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !important;
} }
#listing.mosaic .header { #listing .header {
display: none; display: none;
} }
#listing.mosaic .item div:first-of-type { #listing .item div:first-of-type {
width: 5em; width: 5em;
} }
#listing.mosaic .item div:last-of-type { #listing .item div:last-of-type {
width: calc(100% - 5vw); width: calc(100% - 5vw);
} }
#listing.mosaic.gallery .item div:first-of-type { #listing.gallery .item div:first-of-type {
width: 100%; width: 100%;
height: 12em; height: 12em;
} }
#listing.mosaic.gallery .item div:last-of-type { #listing.gallery .item div:last-of-type {
position: absolute; position: absolute;
bottom: 0.5em; bottom: 0.5em;
padding: 1em; padding: 1em;
@ -127,19 +139,19 @@ body.rtl #listing {
text-align: center; text-align: center;
} }
#listing.mosaic.gallery .item[data-type=image] div:last-of-type { #listing.gallery .item[data-type=image] div:last-of-type {
color: white; color: white;
background: linear-gradient(#0000, #0009); background: linear-gradient(#0000, #0009);
} }
#listing.mosaic.gallery .item i { #listing.gallery .item i {
width: 100%; width: 100%;
margin-right: 0; margin-right: 0;
font-size: 8em; font-size: 8em;
text-align: center; text-align: center;
} }
#listing.mosaic.gallery .item img { #listing.gallery .item img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
@ -149,6 +161,109 @@ body.rtl #listing {
display: none; display: none;
} }
#listing.compact {
flex-direction: column;
width: 100%;
max-width: 100%;
margin: 0;
}
#listing.compact .item {
width: 100%;
margin: 0;
border: 1px solid rgba(0, 0, 0, 0.1);
padding: 0;
border-top: 0;
}
#listing.compact h2 {
display: none;
}
#listing.compact .item div:first-of-type {
width: 3em;
}
#listing.compact .item div:first-of-type i {
font-size: 2em;
}
#listing.compact .item div:first-of-type img {
width: 2em;
height: 2em;
}
#listing.compact .item div:last-of-type {
width: calc(100% - 3em);
display: flex;
align-items: center;
}
#listing.compact .item .name {
width: 50%;
}
#listing.compact .item .size {
width: 25%;
}
#listing.compact .header i {
font-size: 1.5em;
vertical-align: middle;
margin-left: .2em;
}
#listing.compact .header {
display: flex !important;
background: var(--surfacePrimary);
z-index: 999;
padding: .85em;
border: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
#listing.compact .header>div:first-child {
width: 0;
}
#listing.compact .header .name {
margin-right: 3em;
}
#listing.compact .header a {
color: inherit;
}
#listing.compact .header>div:first-child {
width: 0;
}
#listing.compact .name {
font-weight: normal;
}
#listing.compact .header .name {
margin-right: 3em;
}
#listing.compact .header span {
vertical-align: middle;
}
#listing.compact .header i {
opacity: 0;
transition: .1s ease all;
}
#listing.compact .header p:hover i,
#listing.compact .header .active i {
opacity: 1;
}
#listing.compact .header .active {
font-weight: bold;
}
#listing.list { #listing.list {
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
@ -160,14 +275,10 @@ body.rtl #listing {
width: 100%; width: 100%;
margin: 0; margin: 0;
border: 1px solid rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.1);
padding: 0; padding: .5em;
border-top: 0; border-top: 0;
} }
#listing.list h2 {
display: none;
}
#listing .item[aria-selected=true] { #listing .item[aria-selected=true] {
background: var(--blue) !important; background: var(--blue) !important;
color: var(--item-selected) !important; color: var(--item-selected) !important;
@ -200,7 +311,7 @@ body.rtl #listing {
width: 25%; width: 25%;
} }
#listing .item.header { #listing .header {
display: none !important; display: none !important;
background-color: #ccc; background-color: #ccc;
} }
@ -211,20 +322,34 @@ body.rtl #listing {
margin-left: .2em; margin-left: .2em;
} }
#listing.list .item.header { #listing.compact .header,
#listing.list .header {
display: flex !important; display: flex !important;
background: #fafafa; background: var(--surfacePrimary);
border-top-left-radius: 1em;
border-top-right-radius: 1em;
z-index: 999; z-index: 999;
padding: .85em; padding: .85em;
width:100%;
border: 0; border: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1); border-bottom: 1px solid rgba(0, 0, 0, 0.1);
} }
#listing.list .item:first-child {
margin-top: .5em;
border-top-left-radius: 1em;
border-top-right-radius: 1em;
}
#listing.list .item.header>div:first-child { #listing.list .item:last-child {
margin-bottom: .5em;
border-bottom-left-radius: 1em;
border-bottom-right-radius: 1em;
}
#listing.list .header>div:first-child {
width: 0; width: 0;
} }
#listing.list .item.header .name { #listing.list .header .name {
margin-right: 3em; margin-right: 3em;
} }
@ -232,7 +357,7 @@ body.rtl #listing {
color: inherit; color: inherit;
} }
#listing.list .item.header>div:first-child { #listing.list .header>div:first-child {
width: 0; width: 0;
} }
@ -240,7 +365,7 @@ body.rtl #listing {
font-weight: normal; font-weight: normal;
} }
#listing.list .item.header .name { #listing.list .header .name {
margin-right: 3em; margin-right: 3em;
} }
@ -258,7 +383,7 @@ body.rtl #listing {
opacity: 1; opacity: 1;
} }
#listing.list .item.header .active { #listing.list .header .active {
font-weight: bold; font-weight: bold;
} }

View File

@ -7,14 +7,14 @@
} }
@media (max-width: 800px) { @media (max-width: 800px) {
#listing.list .item div:last-of-type{
display:block;
width:100%;
}
body { body {
padding-bottom: 5em; padding-bottom: 5em;
} }
#listing.list .item .size {
display: none;
}
#listing.list .item .name { #listing.list .item .name {
width: 60%; width: 60%;
} }
@ -36,7 +36,9 @@
#listing { #listing {
margin-bottom: 5em; margin-bottom: 5em;
} }
#listing .item {
min-width: 100%
}
body.rtl #listing { body.rtl #listing {
margin-right: unset; margin-right: unset;
} }
@ -117,9 +119,6 @@
@media (max-width: 450px) { @media (max-width: 450px) {
#listing.list .item .modified {
display: none;
}
#listing.list .item .name { #listing.list .item .name {
width: 100%; width: 100%;

View File

@ -11,9 +11,7 @@ export function parseToken(token) {
} }
const data = JSON.parse(Base64.decode(parts[1])); const data = JSON.parse(Base64.decode(parts[1]));
document.cookie = `auth=${token}; path=/`; document.cookie = `auth=${token}; path=/`;
localStorage.setItem("jwt", token); localStorage.setItem("jwt", token);
store.commit("setJWT", token); store.commit("setJWT", token);
store.commit("setSession", generateRandomCode(8)); store.commit("setSession", generateRandomCode(8));
@ -40,7 +38,6 @@ export async function login(username, password, recaptcha) {
}, },
body: JSON.stringify(data), body: JSON.stringify(data),
}); });
const body = await res.text(); const body = await res.text();
if (res.status === 200) { if (res.status === 200) {

View File

@ -11,7 +11,6 @@ const logoURL = `${staticURL}/img/logo.svg`;
const noAuth = window.FileBrowser.NoAuth; const noAuth = window.FileBrowser.NoAuth;
const authMethod = window.FileBrowser.AuthMethod; const authMethod = window.FileBrowser.AuthMethod;
const loginPage = window.FileBrowser.LoginPage; const loginPage = window.FileBrowser.LoginPage;
const theme = window.FileBrowser.Theme;
const enableThumbs = window.FileBrowser.EnableThumbs; const enableThumbs = window.FileBrowser.EnableThumbs;
const resizePreview = window.FileBrowser.ResizePreview; const resizePreview = window.FileBrowser.ResizePreview;
const enableExec = window.FileBrowser.EnableExec; const enableExec = window.FileBrowser.EnableExec;
@ -30,7 +29,6 @@ export {
noAuth, noAuth,
authMethod, authMethod,
loginPage, loginPage,
theme,
enableThumbs, enableThumbs,
resizePreview, resizePreview,
enableExec, enableExec,

View File

@ -7,10 +7,10 @@
<editorBar v-else-if="currentView === 'editor'"></editorBar> <editorBar v-else-if="currentView === 'editor'"></editorBar>
<defaultBar v-else></defaultBar> <defaultBar v-else></defaultBar>
<sidebar></sidebar> <sidebar></sidebar>
<main> <main :class="{ 'dark-mode': isDarkMode }">
<router-view></router-view> <router-view></router-view>
</main> </main>
<prompts></prompts> <prompts :class="{ 'dark-mode': isDarkMode }"></prompts>
<upload-files></upload-files> <upload-files></upload-files>
</div> </div>
</template> </template>
@ -45,7 +45,9 @@ export default {
computed: { computed: {
...mapGetters(["isLogged", "progress", "isListing"]), ...mapGetters(["isLogged", "progress", "isListing"]),
...mapState(["req", "user", "state"]), ...mapState(["req", "user", "state"]),
isDarkMode() {
return this.user.darkMode === true
},
isExecEnabled: () => enableExec, isExecEnabled: () => enableExec,
currentView() { currentView() {
if (this.req.type == undefined) { if (this.req.type == undefined) {
@ -81,4 +83,14 @@ export default {
}, },
}, },
}; };
</script> </script>
<style>
/* Use the class .dark-mode to apply styles conditionally */
.dark-mode {
background: var(--background);
color: var(--textPrimary);
}
</style>

View File

@ -1,10 +1,7 @@
<template> <template>
<div class="dashboard"> <div class="dashboard">
<div id="nav"> <div id="nav">
<div v-if="disabledSettings"> <div v-if="settingsEnabled" class="wrapper">
nothing to see here
</div>
<div v-else class="wrapper">
<ul> <ul>
<router-link to="/settings/profile" <router-link to="/settings/profile"
><li :class="{ active: $route.path === '/settings/profile' }"> ><li :class="{ active: $route.path === '/settings/profile' }">
@ -60,11 +57,10 @@ export default {
this.$store.commit("updateRequest", { name: "Settings" }); this.$store.commit("updateRequest", { name: "Settings" });
}, },
computed: { computed: {
...mapState(["user", "loading","req"]), ...mapState(["user"]),
disableSettings() { settingsEnabled() {
console.log(this.User) return this.user.disableSettings == false;
return this.User.disableSettings == "true" },
}
}, },
}; };
</script> </script>

View File

@ -38,6 +38,7 @@ export default {
dragCounter: 0, dragCounter: 0,
width: window.innerWidth, width: window.innerWidth,
itemWeight: 0, itemWeight: 0,
viewModes: ['list', 'compact', 'normal', 'gallery'],
}; };
}, },
computed: { computed: {
@ -106,8 +107,9 @@ export default {
viewIcon() { viewIcon() {
const icons = { const icons = {
list: "view_module", list: "view_module",
mosaic: "grid_view", compact: "view_module",
"mosaic gallery": "view_list", normal: "grid_view",
gallery: "view_list",
}; };
return icons[this.user.viewMode]; return icons[this.user.viewMode];
}, },
@ -259,21 +261,14 @@ export default {
}, },
switchView: async function () { switchView: async function () {
this.$store.commit("closeHovers"); this.$store.commit("closeHovers");
const modes = { const currentIndex = this.viewModes.indexOf(this.user.viewMode);
list: "mosaic", const nextIndex = (currentIndex + 1) % this.viewModes.length;
mosaic: "mosaic gallery",
"mosaic gallery": "list",
};
const data = { const data = {
id: this.user.id, id: this.user.id,
viewMode: modes[this.user.viewMode] || "list", viewMode: this.viewModes[nextIndex],
}; };
//users.update(data, ["viewMode"]).catch(this.$showError); users.update(data, ["viewMode"]).catch(this.$showError);
this.$store.commit("updateUser", data); this.$store.commit("updateUser", data);
//this.setItemWeight();
//this.fillWindow();
}, },
preventDefault(event) { preventDefault(event) {
// Wrapper around prevent default. // Wrapper around prevent default.
@ -375,7 +370,7 @@ export default {
let columns = Math.floor( let columns = Math.floor(
document.querySelector("main").offsetWidth / this.columnWidth document.querySelector("main").offsetWidth / this.columnWidth
); );
let items = css(["#listing.mosaic .item", ".mosaic#listing .item"]); let items = css(["#listing .item", "#listing .item"]);
if (columns === 0) columns = 1; if (columns === 0) columns = 1;
items.style.width = `calc(${100 / columns}% - 1em)`; items.style.width = `calc(${100 / columns}% - 1em)`;
}, },
@ -583,7 +578,6 @@ export default {
} }
this.$store.commit("updateRequest", {}); this.$store.commit("updateRequest", {});
let uri = url.removeLastDir(this.$route.path) + "/"; let uri = url.removeLastDir(this.$route.path) + "/";
console.log(url)
this.$router.push({ path: uri }); this.$router.push({ path: uri });
}, },
upload: function () { upload: function () {

View File

@ -50,6 +50,7 @@ export default {
dragCounter: 0, dragCounter: 0,
width: window.innerWidth, width: window.innerWidth,
itemWeight: 0, itemWeight: 0,
viewModes: ['list', 'compact', 'normal', 'gallery'],
}; };
}, },
computed: { computed: {
@ -115,8 +116,9 @@ export default {
viewIcon() { viewIcon() {
const icons = { const icons = {
list: "view_module", list: "view_module",
mosaic: "grid_view", compact: "view_module",
"mosaic gallery": "view_list", normal: "grid_view",
gallery: "view_list",
}; };
return icons[this.user.viewMode]; return icons[this.user.viewMode];
}, },
@ -268,21 +270,14 @@ export default {
}, },
switchView: async function () { switchView: async function () {
this.$store.commit("closeHovers"); this.$store.commit("closeHovers");
const modes = { const currentIndex = this.viewModes.indexOf(this.user.viewMode);
list: "mosaic", const nextIndex = (currentIndex + 1) % this.viewModes.length;
mosaic: "mosaic gallery",
"mosaic gallery": "list",
};
const data = { const data = {
id: this.user.id, id: this.user.id,
viewMode: modes[this.user.viewMode] || "list", viewMode: this.viewModes[nextIndex],
}; };
//users.update(data, ["viewMode"]).catch(this.$showError); users.update(data, ["viewMode"]).catch(this.$showError);
this.$store.commit("updateUser", data); this.$store.commit("updateUser", data);
//this.setItemWeight();
//this.fillWindow();
}, },
preventDefault(event) { preventDefault(event) {
// Wrapper around prevent default. // Wrapper around prevent default.
@ -384,7 +379,7 @@ export default {
let columns = Math.floor( let columns = Math.floor(
document.querySelector("main").offsetWidth / this.columnWidth document.querySelector("main").offsetWidth / this.columnWidth
); );
let items = css(["#listing.mosaic .item", ".mosaic#listing .item"]); let items = css(["#listing .item", "#listing .item"]);
if (columns === 0) columns = 1; if (columns === 0) columns = 1;
items.style.width = `calc(${100 / columns}% - 1em)`; items.style.width = `calc(${100 / columns}% - 1em)`;
}, },

View File

@ -87,7 +87,7 @@
multiple multiple
/> />
</div> </div>
<div v-else id="listing" ref="listing" :class="user.viewMode + ' file-icons'"> <div v-else id="listing" ref="listing" :class="listingViewMode + ' file-icons'">
<div> <div>
<div class="item header"> <div class="item header">
<div></div> <div></div>
@ -132,8 +132,11 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="req.numDirs > 0">
<h2 v-if="req.numDirs > 0">{{ $t("files.folders") }}</h2> <div class="header-items">
<h2>{{ $t("files.folders") }}</h2>
</div>
</div>
<div v-if="req.numDirs > 0"> <div v-if="req.numDirs > 0">
<item <item
v-for="item in dirs" v-for="item in dirs"
@ -150,7 +153,11 @@
</item> </item>
</div> </div>
<h2 v-if="req.numFiles > 0">{{ $t("files.files") }}</h2> <div v-if="req.numFiles > 0">
<div class="header-items">
<h2>{{ $t("files.files") }}</h2>
</div>
</div>
<div v-if="req.numFiles > 0"> <div v-if="req.numFiles > 0">
<item <item
v-for="item in files" v-for="item in files"
@ -201,6 +208,15 @@
</div> </div>
</template> </template>
<style>
.header-items {
width: 100% !important;
max-width: 100% !important;
justify-content: center;
}
</style>
<script> <script>
import Vue from "vue"; import Vue from "vue";
import { mapState, mapGetters, mapMutations } from "vuex"; import { mapState, mapGetters, mapMutations } from "vuex";
@ -290,11 +306,15 @@ export default {
viewIcon() { viewIcon() {
const icons = { const icons = {
list: "view_module", list: "view_module",
mosaic: "grid_view", compact: "view_module",
"mosaic gallery": "view_list", normal: "grid_view",
gallery: "view_list",
}; };
return icons[this.user.viewMode]; return icons[this.user.viewMode];
}, },
listingViewMode() {
return this.user.viewMode
},
headerButtons() { headerButtons() {
return { return {
select: this.selectedCount > 0, select: this.selectedCount > 0,
@ -527,7 +547,7 @@ export default {
let columns = Math.floor( let columns = Math.floor(
document.querySelector("main").offsetWidth / this.columnWidth document.querySelector("main").offsetWidth / this.columnWidth
); );
let items = css(["#listing.mosaic .item", ".mosaic#listing .item"]); let items = css(["#listing .item", "#listing .item"]);
if (columns === 0) columns = 1; if (columns === 0) columns = 1;
items.style.width = `calc(${100 / columns}% - 1em)`; items.style.width = `calc(${100 / columns}% - 1em)`;
}, },

View File

@ -71,15 +71,6 @@
{{ $t("settings.disableUsedDiskPercentage") }} {{ $t("settings.disableUsedDiskPercentage") }}
</p> </p>
<p>
<label for="theme">{{ $t("settings.themes.title") }}</label>
<themes
class="input input--block"
:theme.sync="settings.frontend.theme"
id="theme"
></themes>
</p>
<p> <p>
<label for="branding-name">{{ $t("settings.instanceName") }}</label> <label for="branding-name">{{ $t("settings.instanceName") }}</label>
<input <input
@ -194,13 +185,11 @@ import { settings as api } from "@/api";
import { enableExec } from "@/utils/constants"; import { enableExec } from "@/utils/constants";
import UserForm from "@/components/settings/UserForm"; import UserForm from "@/components/settings/UserForm";
import Rules from "@/components/settings/Rules"; import Rules from "@/components/settings/Rules";
import Themes from "@/components/settings/Themes";
import Errors from "@/views/Errors"; import Errors from "@/views/Errors";
export default { export default {
name: "settings", name: "settings",
components: { components: {
Themes,
UserForm, UserForm,
Rules, Rules,
Errors, Errors,

View File

@ -7,6 +7,10 @@
</div> </div>
<div class="card-content"> <div class="card-content">
<p>
<input type="checkbox" v-model="darkMode" />
Dark Mode
</p>
<p> <p>
<input type="checkbox" v-model="hideDotfiles" /> <input type="checkbox" v-model="hideDotfiles" />
{{ $t("settings.hideDotfiles") }} {{ $t("settings.hideDotfiles") }}
@ -19,6 +23,11 @@
<input type="checkbox" v-model="dateFormat" /> <input type="checkbox" v-model="dateFormat" />
{{ $t("settings.setDateFormat") }} {{ $t("settings.setDateFormat") }}
</p> </p>
<h3>Listing View Style</h3>
<ViewMode
class="input input--block"
:viewMode.sync="viewMode"
></ViewMode>
<h3>{{ $t("settings.language") }}</h3> <h3>{{ $t("settings.language") }}</h3>
<languages <languages
class="input input--block" class="input input--block"
@ -75,11 +84,13 @@
import { mapState, mapMutations } from "vuex"; import { mapState, mapMutations } from "vuex";
import { users as api } from "@/api"; import { users as api } from "@/api";
import Languages from "@/components/settings/Languages"; import Languages from "@/components/settings/Languages";
import ViewMode from "@/components/settings/ViewMode";
import i18n, { rtlLanguages } from "@/i18n"; import i18n, { rtlLanguages } from "@/i18n";
export default { export default {
name: "settings", name: "settings",
components: { components: {
ViewMode,
Languages, Languages,
}, },
data: function () { data: function () {
@ -89,6 +100,8 @@ export default {
hideDotfiles: false, hideDotfiles: false,
singleClick: false, singleClick: false,
dateFormat: false, dateFormat: false,
darkMode: false,
viewMode: "list",
locale: "", locale: "",
}; };
}, },
@ -109,8 +122,14 @@ export default {
}, },
}, },
created() { created() {
if (typeof this.user.darkMode === 'undefined') {
this.darkMode = false;
} else {
this.darkMode = this.user.darkMode
}
this.setLoading(false); this.setLoading(false);
this.locale = this.user.locale; this.locale = this.user.locale;
this.viewMode = this.user.viewMode;
this.hideDotfiles = this.user.hideDotfiles; this.hideDotfiles = this.user.hideDotfiles;
this.singleClick = this.user.singleClick; this.singleClick = this.user.singleClick;
this.dateFormat = this.user.dateFormat; this.dateFormat = this.user.dateFormat;
@ -135,11 +154,12 @@ export default {
}, },
async updateSettings(event) { async updateSettings(event) {
event.preventDefault(); event.preventDefault();
try { try {
const data = { const data = {
id: this.user.id, id: this.user.id,
locale: this.locale, locale: this.locale,
darkMode: this.darkMode,
viewMode: this.viewMode,
hideDotfiles: this.hideDotfiles, hideDotfiles: this.hideDotfiles,
singleClick: this.singleClick, singleClick: this.singleClick,
dateFormat: this.dateFormat, dateFormat: this.dateFormat,
@ -149,6 +169,8 @@ export default {
rtlLanguages.includes(i18n.locale); rtlLanguages.includes(i18n.locale);
await api.update(data, [ await api.update(data, [
"locale", "locale",
"darkMode",
"viewMode",
"hideDotfiles", "hideDotfiles",
"singleClick", "singleClick",
"dateFormat", "dateFormat",