Merge branch 'master' into delete_scene

This commit is contained in:
WithoutPants
2019-08-25 17:41:32 +10:00
committed by GitHub
17 changed files with 578 additions and 47 deletions

View File

@@ -4,8 +4,16 @@ fragment ConfigGeneralData on ConfigGeneralResult {
generatedPath generatedPath
} }
fragment ConfigInterfaceData on ConfigInterfaceResult {
css
cssEnabled
}
fragment ConfigData on ConfigResult { fragment ConfigData on ConfigResult {
general { general {
...ConfigGeneralData ...ConfigGeneralData
} }
interface {
...ConfigInterfaceData
}
} }

View File

@@ -2,4 +2,10 @@ mutation ConfigureGeneral($input: ConfigGeneralInput!) {
configureGeneral(input: $input) { configureGeneral(input: $input) {
...ConfigGeneralData ...ConfigGeneralData
} }
}
mutation ConfigureInterface($input: ConfigInterfaceInput!) {
configureInterface(input: $input) {
...ConfigInterfaceData
}
} }

View File

@@ -92,6 +92,7 @@ type Mutation {
"""Change general configuration options""" """Change general configuration options"""
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult! configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult!
} }
type Subscription { type Subscription {

View File

@@ -16,7 +16,20 @@ type ConfigGeneralResult {
generatedPath: String! generatedPath: String!
} }
input ConfigInterfaceInput {
"""Custom CSS"""
css: String
cssEnabled: Boolean
}
type ConfigInterfaceResult {
"""Custom CSS"""
css: String
cssEnabled: Boolean
}
"""All configuration settings""" """All configuration settings"""
type ConfigResult { type ConfigResult {
general: ConfigGeneralResult! general: ConfigGeneralResult!
interface: ConfigInterfaceResult!
} }

View File

@@ -3,10 +3,11 @@ package api
import ( import (
"context" "context"
"fmt" "fmt"
"path/filepath"
"github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
"path/filepath"
) )
func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.ConfigGeneralInput) (*models.ConfigGeneralResult, error) { func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.ConfigGeneralInput) (*models.ConfigGeneralResult, error) {
@@ -41,3 +42,23 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
return makeConfigGeneralResult(), nil return makeConfigGeneralResult(), nil
} }
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.ConfigInterfaceInput) (*models.ConfigInterfaceResult, error) {
css := ""
if input.CSS != nil {
css = *input.CSS
}
config.SetCSS(css)
if input.CSSEnabled != nil {
config.Set(config.CSSEnabled, *input.CSSEnabled)
}
if err := config.Write(); err != nil {
return makeConfigInterfaceResult(), err
}
return makeConfigInterfaceResult(), nil
}

View File

@@ -2,6 +2,7 @@ package api
import ( import (
"context" "context"
"github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
@@ -21,7 +22,8 @@ func (r *queryResolver) Directories(ctx context.Context, path *string) ([]string
func makeConfigResult() *models.ConfigResult { func makeConfigResult() *models.ConfigResult {
return &models.ConfigResult{ return &models.ConfigResult{
General: makeConfigGeneralResult(), General: makeConfigGeneralResult(),
Interface: makeConfigInterfaceResult(),
} }
} }
@@ -32,3 +34,12 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult {
GeneratedPath: config.GetGeneratedPath(), GeneratedPath: config.GetGeneratedPath(),
} }
} }
func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
css := config.GetCSS()
cssEnabled := config.GetCSSEnabled()
return &models.ConfigInterfaceResult{
CSS: &css,
CSSEnabled: &cssEnabled,
}
}

View File

@@ -5,6 +5,15 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"runtime/debug"
"strconv"
"strings"
"github.com/99designs/gqlgen/handler" "github.com/99designs/gqlgen/handler"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/go-chi/chi/middleware" "github.com/go-chi/chi/middleware"
@@ -16,14 +25,6 @@ import (
"github.com/stashapp/stash/pkg/manager/paths" "github.com/stashapp/stash/pkg/manager/paths"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"runtime/debug"
"strconv"
"strings"
) )
var uiBox *packr.Box var uiBox *packr.Box
@@ -72,6 +73,21 @@ func Start() {
r.Mount("/scene", sceneRoutes{}.Routes()) r.Mount("/scene", sceneRoutes{}.Routes())
r.Mount("/studio", studioRoutes{}.Routes()) r.Mount("/studio", studioRoutes{}.Routes())
r.HandleFunc("/css", func(w http.ResponseWriter, r *http.Request) {
if !config.GetCSSEnabled() {
return
}
// search for custom.css in current directory, then $HOME/.stash
fn := config.GetCSSPath()
exists, _ := utils.FileExists(fn)
if !exists {
return
}
http.ServeFile(w, r, fn)
})
// Serve the setup UI // Serve the setup UI
r.HandleFunc("/setup*", func(w http.ResponseWriter, r *http.Request) { r.HandleFunc("/setup*", func(w http.ResponseWriter, r *http.Request) {
ext := path.Ext(r.URL.Path) ext := path.Ext(r.URL.Path)

View File

@@ -1,7 +1,10 @@
package config package config
import ( import (
"io/ioutil"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/stashapp/stash/pkg/utils"
) )
const Stash = "stash" const Stash = "stash"
@@ -15,6 +18,8 @@ const Database = "database"
const Host = "host" const Host = "host"
const Port = "port" const Port = "port"
const CSSEnabled = "cssEnabled"
func Set(key string, value interface{}) { func Set(key string, value interface{}) {
viper.Set(key, value) viper.Set(key, value)
} }
@@ -51,6 +56,46 @@ func GetPort() int {
return viper.GetInt(Port) return viper.GetInt(Port)
} }
func GetCSSPath() string {
// search for custom.css in current directory, then $HOME/.stash
fn := "custom.css"
exists, _ := utils.FileExists(fn)
if !exists {
fn = "$HOME/.stash/" + fn
}
return fn
}
func GetCSS() string {
fn := GetCSSPath()
exists, _ := utils.FileExists(fn)
if !exists {
return ""
}
buf, err := ioutil.ReadFile(fn)
if err != nil {
return ""
}
return string(buf)
}
func SetCSS(css string) {
fn := GetCSSPath()
buf := []byte(css)
ioutil.WriteFile(fn, buf, 0777)
}
func GetCSSEnabled() bool {
return viper.GetBool(CSSEnabled)
}
func IsValid() bool { func IsValid() bool {
setPaths := viper.IsSet(Stash) && viper.IsSet(Cache) && viper.IsSet(Generated) && viper.IsSet(Metadata) setPaths := viper.IsSet(Stash) && viper.IsSet(Cache) && viper.IsSet(Generated) && viper.IsSet(Metadata)
// TODO: check valid paths // TODO: check valid paths

View File

@@ -1,8 +1,9 @@
package manager package manager
import ( import (
"fmt" "net"
"github.com/fsnotify/fsnotify" "sync"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/stashapp/stash/pkg/ffmpeg" "github.com/stashapp/stash/pkg/ffmpeg"
@@ -10,8 +11,6 @@ import (
"github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/manager/paths" "github.com/stashapp/stash/pkg/manager/paths"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
"net"
"sync"
) )
type singleton struct { type singleton struct {
@@ -71,12 +70,15 @@ func initConfig() {
// Set generated to the metadata path for backwards compat // Set generated to the metadata path for backwards compat
viper.SetDefault(config.Generated, viper.GetString(config.Metadata)) viper.SetDefault(config.Generated, viper.GetString(config.Metadata))
// Disabling config watching due to race condition issue
// See: https://github.com/spf13/viper/issues/174
// Changes to the config outside the system will require a restart
// Watch for changes // Watch for changes
viper.WatchConfig() // viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) { // viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name) // fmt.Println("Config file changed:", e.Name)
instance.refreshConfig() // instance.refreshConfig()
}) // })
//viper.Set("stash", []string{"/", "/stuff"}) //viper.Set("stash", []string{"/", "/stuff"})
//viper.WriteConfig() //viper.WriteConfig()
@@ -92,15 +94,15 @@ func initFlags() {
} }
} }
func initEnvs() { func initEnvs() {
viper.SetEnvPrefix("stash") // will be uppercased automatically viper.SetEnvPrefix("stash") // will be uppercased automatically
viper.BindEnv("host") // STASH_HOST viper.BindEnv("host") // STASH_HOST
viper.BindEnv("port") // STASH_PORT viper.BindEnv("port") // STASH_PORT
viper.BindEnv("stash") // STASH_STASH viper.BindEnv("stash") // STASH_STASH
viper.BindEnv("generated") // STASH_GENERATED viper.BindEnv("generated") // STASH_GENERATED
viper.BindEnv("metadata") // STASH_METADATA viper.BindEnv("metadata") // STASH_METADATA
viper.BindEnv("cache") // STASH_CACHE viper.BindEnv("cache") // STASH_CACHE
} }
func initFFMPEG() { func initFFMPEG() {
configDirectory := paths.GetConfigDirectory() configDirectory := paths.GetConfigDirectory()

View File

@@ -47,7 +47,7 @@ func (t *ScanTask) scanGallery() {
tx := database.DB.MustBeginTx(ctx, nil) tx := database.DB.MustBeginTx(ctx, nil)
gallery, _ = qb.FindByChecksum(checksum, tx) gallery, _ = qb.FindByChecksum(checksum, tx)
if gallery != nil { if gallery != nil {
exists, _ := utils.FileExists(t.FilePath) exists, _ := utils.FileExists(gallery.Path)
if exists { if exists {
logger.Infof("%s already exists. Duplicate of %s ", t.FilePath, gallery.Path) logger.Infof("%s already exists. Duplicate of %s ", t.FilePath, gallery.Path)
} else { } else {
@@ -102,7 +102,7 @@ func (t *ScanTask) scanScene() {
ctx := context.TODO() ctx := context.TODO()
tx := database.DB.MustBeginTx(ctx, nil) tx := database.DB.MustBeginTx(ctx, nil)
if scene != nil { if scene != nil {
exists, _ := utils.FileExists(t.FilePath) exists, _ := utils.FileExists(scene.Path)
if exists { if exists {
logger.Infof("%s already exists. Duplicate of %s ", t.FilePath, scene.Path) logger.Infof("%s already exists. Duplicate of %s ", t.FilePath, scene.Path)
} else { } else {

View File

@@ -56,8 +56,14 @@ type ComplexityRoot struct {
Stashes func(childComplexity int) int Stashes func(childComplexity int) int
} }
ConfigInterfaceResult struct {
CSS func(childComplexity int) int
CSSEnabled func(childComplexity int) int
}
ConfigResult struct { ConfigResult struct {
General func(childComplexity int) int General func(childComplexity int) int
Interface func(childComplexity int) int
} }
FindGalleriesResultType struct { FindGalleriesResultType struct {
@@ -107,6 +113,7 @@ type ComplexityRoot struct {
Mutation struct { Mutation struct {
ConfigureGeneral func(childComplexity int, input ConfigGeneralInput) int ConfigureGeneral func(childComplexity int, input ConfigGeneralInput) int
ConfigureInterface func(childComplexity int, input ConfigInterfaceInput) int
PerformerCreate func(childComplexity int, input PerformerCreateInput) int PerformerCreate func(childComplexity int, input PerformerCreateInput) int
PerformerDestroy func(childComplexity int, input PerformerDestroyInput) int PerformerDestroy func(childComplexity int, input PerformerDestroyInput) int
PerformerUpdate func(childComplexity int, input PerformerUpdateInput) int PerformerUpdate func(childComplexity int, input PerformerUpdateInput) int
@@ -300,6 +307,7 @@ type MutationResolver interface {
TagUpdate(ctx context.Context, input TagUpdateInput) (*Tag, error) TagUpdate(ctx context.Context, input TagUpdateInput) (*Tag, error)
TagDestroy(ctx context.Context, input TagDestroyInput) (bool, error) TagDestroy(ctx context.Context, input TagDestroyInput) (bool, error)
ConfigureGeneral(ctx context.Context, input ConfigGeneralInput) (*ConfigGeneralResult, error) ConfigureGeneral(ctx context.Context, input ConfigGeneralInput) (*ConfigGeneralResult, error)
ConfigureInterface(ctx context.Context, input ConfigInterfaceInput) (*ConfigInterfaceResult, error)
} }
type PerformerResolver interface { type PerformerResolver interface {
Name(ctx context.Context, obj *Performer) (*string, error) Name(ctx context.Context, obj *Performer) (*string, error)
@@ -426,6 +434,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ConfigGeneralResult.Stashes(childComplexity), true return e.complexity.ConfigGeneralResult.Stashes(childComplexity), true
case "ConfigInterfaceResult.css":
if e.complexity.ConfigInterfaceResult.CSS == nil {
break
}
return e.complexity.ConfigInterfaceResult.CSS(childComplexity), true
case "ConfigInterfaceResult.cssEnabled":
if e.complexity.ConfigInterfaceResult.CSSEnabled == nil {
break
}
return e.complexity.ConfigInterfaceResult.CSSEnabled(childComplexity), true
case "ConfigResult.general": case "ConfigResult.general":
if e.complexity.ConfigResult.General == nil { if e.complexity.ConfigResult.General == nil {
break break
@@ -433,6 +455,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ConfigResult.General(childComplexity), true return e.complexity.ConfigResult.General(childComplexity), true
case "ConfigResult.interface":
if e.complexity.ConfigResult.Interface == nil {
break
}
return e.complexity.ConfigResult.Interface(childComplexity), true
case "FindGalleriesResultType.count": case "FindGalleriesResultType.count":
if e.complexity.FindGalleriesResultType.Count == nil { if e.complexity.FindGalleriesResultType.Count == nil {
break break
@@ -592,6 +621,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.ConfigureGeneral(childComplexity, args["input"].(ConfigGeneralInput)), true return e.complexity.Mutation.ConfigureGeneral(childComplexity, args["input"].(ConfigGeneralInput)), true
case "Mutation.configureInterface":
if e.complexity.Mutation.ConfigureInterface == nil {
break
}
args, err := ec.field_Mutation_configureInterface_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.ConfigureInterface(childComplexity, args["input"].(ConfigInterfaceInput)), true
case "Mutation.performerCreate": case "Mutation.performerCreate":
if e.complexity.Mutation.PerformerCreate == nil { if e.complexity.Mutation.PerformerCreate == nil {
break break
@@ -1895,6 +1936,7 @@ type Mutation {
"""Change general configuration options""" """Change general configuration options"""
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult! configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult!
} }
type Subscription { type Subscription {
@@ -1925,9 +1967,22 @@ type ConfigGeneralResult {
generatedPath: String! generatedPath: String!
} }
input ConfigInterfaceInput {
"""Custom CSS"""
css: String
cssEnabled: Boolean
}
type ConfigInterfaceResult {
"""Custom CSS"""
css: String
cssEnabled: Boolean
}
"""All configuration settings""" """All configuration settings"""
type ConfigResult { type ConfigResult {
general: ConfigGeneralResult! general: ConfigGeneralResult!
interface: ConfigInterfaceResult!
}`}, }`},
&ast.Source{Name: "graphql/schema/types/filters.graphql", Input: `enum SortDirectionEnum { &ast.Source{Name: "graphql/schema/types/filters.graphql", Input: `enum SortDirectionEnum {
ASC ASC
@@ -2315,6 +2370,20 @@ func (ec *executionContext) field_Mutation_configureGeneral_args(ctx context.Con
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_configureInterface_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 ConfigInterfaceInput
if tmp, ok := rawArgs["input"]; ok {
arg0, err = ec.unmarshalNConfigInterfaceInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigInterfaceInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_performerCreate_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Mutation_performerCreate_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@@ -2952,6 +3021,54 @@ func (ec *executionContext) _ConfigGeneralResult_generatedPath(ctx context.Conte
return ec.marshalNString2string(ctx, field.Selections, res) return ec.marshalNString2string(ctx, field.Selections, res)
} }
func (ec *executionContext) _ConfigInterfaceResult_css(ctx context.Context, field graphql.CollectedField, obj *ConfigInterfaceResult) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
rctx := &graphql.ResolverContext{
Object: "ConfigInterfaceResult",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithResolverContext(ctx, rctx)
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.CSS, nil
})
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _ConfigInterfaceResult_cssEnabled(ctx context.Context, field graphql.CollectedField, obj *ConfigInterfaceResult) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
rctx := &graphql.ResolverContext{
Object: "ConfigInterfaceResult",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithResolverContext(ctx, rctx)
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.CSSEnabled, nil
})
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*bool)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res)
}
func (ec *executionContext) _ConfigResult_general(ctx context.Context, field graphql.CollectedField, obj *ConfigResult) graphql.Marshaler { func (ec *executionContext) _ConfigResult_general(ctx context.Context, field graphql.CollectedField, obj *ConfigResult) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field) ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }() defer func() { ec.Tracer.EndFieldExecution(ctx) }()
@@ -2979,6 +3096,33 @@ func (ec *executionContext) _ConfigResult_general(ctx context.Context, field gra
return ec.marshalNConfigGeneralResult2ᚖgithubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigGeneralResult(ctx, field.Selections, res) return ec.marshalNConfigGeneralResult2ᚖgithubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigGeneralResult(ctx, field.Selections, res)
} }
func (ec *executionContext) _ConfigResult_interface(ctx context.Context, field graphql.CollectedField, obj *ConfigResult) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
rctx := &graphql.ResolverContext{
Object: "ConfigResult",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithResolverContext(ctx, rctx)
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Interface, nil
})
if resTmp == nil {
if !ec.HasError(rctx) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*ConfigInterfaceResult)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalNConfigInterfaceResult2ᚖgithubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigInterfaceResult(ctx, field.Selections, res)
}
func (ec *executionContext) _FindGalleriesResultType_count(ctx context.Context, field graphql.CollectedField, obj *FindGalleriesResultType) graphql.Marshaler { func (ec *executionContext) _FindGalleriesResultType_count(ctx context.Context, field graphql.CollectedField, obj *FindGalleriesResultType) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field) ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }() defer func() { ec.Tracer.EndFieldExecution(ctx) }()
@@ -4020,6 +4164,40 @@ func (ec *executionContext) _Mutation_configureGeneral(ctx context.Context, fiel
return ec.marshalNConfigGeneralResult2ᚖgithubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigGeneralResult(ctx, field.Selections, res) return ec.marshalNConfigGeneralResult2ᚖgithubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigGeneralResult(ctx, field.Selections, res)
} }
func (ec *executionContext) _Mutation_configureInterface(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
rctx := &graphql.ResolverContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithResolverContext(ctx, rctx)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_configureInterface_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
rctx.Args = args
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().ConfigureInterface(rctx, args["input"].(ConfigInterfaceInput))
})
if resTmp == nil {
if !ec.HasError(rctx) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*ConfigInterfaceResult)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalNConfigInterfaceResult2ᚖgithubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigInterfaceResult(ctx, field.Selections, res)
}
func (ec *executionContext) _Performer_id(ctx context.Context, field graphql.CollectedField, obj *Performer) graphql.Marshaler { func (ec *executionContext) _Performer_id(ctx context.Context, field graphql.CollectedField, obj *Performer) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field) ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }() defer func() { ec.Tracer.EndFieldExecution(ctx) }()
@@ -8118,6 +8296,30 @@ func (ec *executionContext) unmarshalInputConfigGeneralInput(ctx context.Context
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputConfigInterfaceInput(ctx context.Context, v interface{}) (ConfigInterfaceInput, error) {
var it ConfigInterfaceInput
var asMap = v.(map[string]interface{})
for k, v := range asMap {
switch k {
case "css":
var err error
it.CSS, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "cssEnabled":
var err error
it.CSSEnabled, err = ec.unmarshalOBoolean2ᚖbool(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputFindFilterType(ctx context.Context, v interface{}) (FindFilterType, error) { func (ec *executionContext) unmarshalInputFindFilterType(ctx context.Context, v interface{}) (FindFilterType, error) {
var it FindFilterType var it FindFilterType
var asMap = v.(map[string]interface{}) var asMap = v.(map[string]interface{})
@@ -8967,6 +9169,32 @@ func (ec *executionContext) _ConfigGeneralResult(ctx context.Context, sel ast.Se
return out return out
} }
var configInterfaceResultImplementors = []string{"ConfigInterfaceResult"}
func (ec *executionContext) _ConfigInterfaceResult(ctx context.Context, sel ast.SelectionSet, obj *ConfigInterfaceResult) graphql.Marshaler {
fields := graphql.CollectFields(ec.RequestContext, sel, configInterfaceResultImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ConfigInterfaceResult")
case "css":
out.Values[i] = ec._ConfigInterfaceResult_css(ctx, field, obj)
case "cssEnabled":
out.Values[i] = ec._ConfigInterfaceResult_cssEnabled(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var configResultImplementors = []string{"ConfigResult"} var configResultImplementors = []string{"ConfigResult"}
func (ec *executionContext) _ConfigResult(ctx context.Context, sel ast.SelectionSet, obj *ConfigResult) graphql.Marshaler { func (ec *executionContext) _ConfigResult(ctx context.Context, sel ast.SelectionSet, obj *ConfigResult) graphql.Marshaler {
@@ -8983,6 +9211,11 @@ func (ec *executionContext) _ConfigResult(ctx context.Context, sel ast.Selection
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "interface":
out.Values[i] = ec._ConfigResult_interface(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@@ -9347,6 +9580,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "configureInterface":
out.Values[i] = ec._Mutation_configureInterface(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@@ -10947,6 +11185,24 @@ func (ec *executionContext) marshalNConfigGeneralResult2ᚖgithubᚗcomᚋstasha
return ec._ConfigGeneralResult(ctx, sel, v) return ec._ConfigGeneralResult(ctx, sel, v)
} }
func (ec *executionContext) unmarshalNConfigInterfaceInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigInterfaceInput(ctx context.Context, v interface{}) (ConfigInterfaceInput, error) {
return ec.unmarshalInputConfigInterfaceInput(ctx, v)
}
func (ec *executionContext) marshalNConfigInterfaceResult2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigInterfaceResult(ctx context.Context, sel ast.SelectionSet, v ConfigInterfaceResult) graphql.Marshaler {
return ec._ConfigInterfaceResult(ctx, sel, &v)
}
func (ec *executionContext) marshalNConfigInterfaceResult2ᚖgithubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigInterfaceResult(ctx context.Context, sel ast.SelectionSet, v *ConfigInterfaceResult) graphql.Marshaler {
if v == nil {
if !ec.HasError(graphql.GetResolverContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._ConfigInterfaceResult(ctx, sel, v)
}
func (ec *executionContext) marshalNConfigResult2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigResult(ctx context.Context, sel ast.SelectionSet, v ConfigResult) graphql.Marshaler { func (ec *executionContext) marshalNConfigResult2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐConfigResult(ctx context.Context, sel ast.SelectionSet, v ConfigResult) graphql.Marshaler {
return ec._ConfigResult(ctx, sel, &v) return ec._ConfigResult(ctx, sel, &v)
} }

View File

@@ -26,9 +26,22 @@ type ConfigGeneralResult struct {
GeneratedPath string `json:"generatedPath"` GeneratedPath string `json:"generatedPath"`
} }
type ConfigInterfaceInput struct {
// Custom CSS
CSS *string `json:"css"`
CSSEnabled *bool `json:"cssEnabled"`
}
type ConfigInterfaceResult struct {
// Custom CSS
CSS *string `json:"css"`
CSSEnabled *bool `json:"cssEnabled"`
}
// All configuration settings // All configuration settings
type ConfigResult struct { type ConfigResult struct {
General *ConfigGeneralResult `json:"general"` General *ConfigGeneralResult `json:"general"`
Interface *ConfigInterfaceResult `json:"interface"`
} }
type FindFilterType struct { type FindFilterType struct {

View File

@@ -1,19 +1,54 @@
import { import {
Button,
Checkbox, Checkbox,
Divider,
FormGroup, FormGroup,
H4, H4,
Spinner,
TextArea
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import _ from "lodash"; import _ from "lodash";
import React, { FunctionComponent } from "react"; import React, { FunctionComponent, useEffect, useState } from "react";
import { useInterfaceLocalForage } from "../../hooks/LocalForage"; import { useInterfaceLocalForage } from "../../hooks/LocalForage";
import { StashService } from "../../core/StashService";
import { ErrorUtils } from "../../utils/errors";
import { ToastUtils } from "../../utils/toasts";
interface IProps {} interface IProps {}
export const SettingsInterfacePanel: FunctionComponent<IProps> = () => { export const SettingsInterfacePanel: FunctionComponent<IProps> = () => {
const {data, setData} = useInterfaceLocalForage(); const {data, setData} = useInterfaceLocalForage();
const config = StashService.useConfiguration();
const [css, setCSS] = useState<string>();
const [cssEnabled, setCSSEnabled] = useState<boolean>();
const updateInterfaceConfig = StashService.useConfigureInterface({
css,
cssEnabled
});
useEffect(() => {
if (!config.data || !config.data.configuration || !!config.error) { return; }
if (!!config.data.configuration.interface) {
setCSS(config.data.configuration.interface.css || "");
setCSSEnabled(config.data.configuration.interface.cssEnabled || false);
}
}, [config.data]);
async function onSave() {
try {
const result = await updateInterfaceConfig();
console.log(result);
ToastUtils.success("Updated config");
} catch (e) {
ErrorUtils.handle(e);
}
}
return ( return (
<> <>
{!!config.error ? <h1>{config.error.message}</h1> : undefined}
{(!config.data || !config.data.configuration || config.loading) ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined}
<H4>User Interface</H4> <H4>User Interface</H4>
<FormGroup <FormGroup
label="Scene / Marker Wall" label="Scene / Marker Wall"
@@ -40,6 +75,29 @@ export const SettingsInterfacePanel: FunctionComponent<IProps> = () => {
}} }}
/> />
</FormGroup> </FormGroup>
<FormGroup
label="Custom CSS"
helperText="Page must be reloaded for changes to take effect."
>
<Checkbox
checked={cssEnabled}
label="Custom CSS enabled"
onChange={() => {
setCSSEnabled(!cssEnabled)
}}
/>
<TextArea
value={css}
onChange={(e: any) => setCSS(e.target.value)}
fill={true}
rows={16}>
</TextArea>
<Divider />
<Button intent="primary" onClick={() => onSave()}>Save</Button>
</FormGroup>
</> </>
); );
}; };

View File

@@ -1,5 +1,5 @@
import _ from "lodash"; import _ from "lodash";
import React, { FunctionComponent, useRef, useState } from "react"; import React, { FunctionComponent, useRef, useState, useEffect } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import * as GQL from "../../core/generated-graphql"; import * as GQL from "../../core/generated-graphql";
import { useInterfaceLocalForage } from "../../hooks/LocalForage"; import { useInterfaceLocalForage } from "../../hooks/LocalForage";
@@ -17,6 +17,10 @@ interface IWallItemProps {
export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProps) => { export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProps) => {
const [videoPath, setVideoPath] = useState<string | undefined>(undefined); const [videoPath, setVideoPath] = useState<string | undefined>(undefined);
const [previewPath, setPreviewPath] = useState<string>("");
const [screenshotPath, setScreenshotPath] = useState<string>("");
const [title, setTitle] = useState<string>("");
const [tags, setTags] = useState<JSX.Element[]>([]);
const videoHoverHook = VideoHoverHook.useVideoHover({resetOnMouseLeave: true}); const videoHoverHook = VideoHoverHook.useVideoHover({resetOnMouseLeave: true});
const interfaceSettings = useInterfaceLocalForage(); const interfaceSettings = useInterfaceLocalForage();
const showTextContainer = !!interfaceSettings.data ? interfaceSettings.data.wall.textContainerEnabled : true; const showTextContainer = !!interfaceSettings.data ? interfaceSettings.data.wall.textContainerEnabled : true;
@@ -68,18 +72,25 @@ export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProp
} }
} }
let previewSrc: string = ""; useEffect(() => {
let title: string = ""; if (!!props.sceneMarker) {
let tags: JSX.Element[] = []; setPreviewPath(props.sceneMarker.preview);
if (!!props.sceneMarker) { setTitle(`${props.sceneMarker!.title} - ${TextUtils.secondsToTimestamp(props.sceneMarker.seconds)}`);
previewSrc = props.sceneMarker.preview; const thisTags = props.sceneMarker.tags.map((tag) => (<span key={tag.id}>{tag.name}</span>));
title = `${props.sceneMarker!.title} - ${TextUtils.secondsToTimestamp(props.sceneMarker.seconds)}`; thisTags.unshift(<span key={props.sceneMarker.primary_tag.id}>{props.sceneMarker.primary_tag.name}</span>);
tags = props.sceneMarker.tags.map((tag) => (<span key={tag.id}>{tag.name}</span>)); setTags(thisTags);
tags.unshift(<span key={props.sceneMarker.primary_tag.id}>{props.sceneMarker.primary_tag.name}</span>); } else if (!!props.scene) {
} else if (!!props.scene) { setPreviewPath(props.scene.paths.webp || "");
previewSrc = props.scene.paths.webp || ""; setScreenshotPath(props.scene.paths.screenshot || "");
title = props.scene.title || ""; setTitle(props.scene.title || "");
// tags = props.scene.tags.map((tag) => (<span key={tag.id}>{tag.name}</span>)); // tags = props.scene.tags.map((tag) => (<span key={tag.id}>{tag.name}</span>));
}
}, [props.sceneMarker, props.scene]);
function previewNotFound() {
if (previewPath !== screenshotPath) {
setPreviewPath(screenshotPath);
}
} }
const className = ["scene-wall-item-container"]; const className = ["scene-wall-item-container"];
@@ -99,12 +110,13 @@ export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProp
<Link onClick={() => onClick()} to={linkSrc}> <Link onClick={() => onClick()} to={linkSrc}>
<video <video
src={videoPath} src={videoPath}
poster={screenshotPath}
style={videoHoverHook.isHovering.current ? {} : {display: "none"}} style={videoHoverHook.isHovering.current ? {} : {display: "none"}}
autoPlay={true} autoPlay={true}
loop={true} loop={true}
ref={videoHoverHook.videoEl} ref={videoHoverHook.videoEl}
/> />
<img src={previewSrc} /> <img src={previewPath || screenshotPath} onError={() => previewNotFound()} />
{showTextContainer ? {showTextContainer ?
<div className="scene-wall-item-text-container"> <div className="scene-wall-item-text-container">
<div style={{lineHeight: 1}}> <div style={{lineHeight: 1}}>

View File

@@ -166,6 +166,10 @@ export class StashService {
return GQL.useConfigureGeneral({ variables: { input }, refetchQueries: ["Configuration"] }); return GQL.useConfigureGeneral({ variables: { input }, refetchQueries: ["Configuration"] });
} }
public static useConfigureInterface(input: GQL.ConfigInterfaceInput) {
return GQL.useConfigureInterface({ variables: { input }, refetchQueries: ["Configuration"] });
}
public static queryScrapeFreeones(performerName: string) { public static queryScrapeFreeones(performerName: string) {
return StashService.client.query<GQL.ScrapeFreeonesQuery>({ return StashService.client.query<GQL.ScrapeFreeonesQuery>({
query: GQL.ScrapeFreeonesDocument, query: GQL.ScrapeFreeonesDocument,

View File

@@ -1,6 +1,6 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
// Generated in 2019-08-15T18:05:18+10:00 // Generated in 2019-08-23T07:28:41+10:00
export type Maybe<T> = T | undefined; export type Maybe<T> = T | undefined;
export interface SceneFilterType { export interface SceneFilterType {
@@ -245,6 +245,13 @@ export interface ConfigGeneralInput {
generatedPath?: Maybe<string>; generatedPath?: Maybe<string>;
} }
export interface ConfigInterfaceInput {
/** Custom CSS */
css?: Maybe<string>;
cssEnabled?: Maybe<boolean>;
}
export enum CriterionModifier { export enum CriterionModifier {
Equals = "EQUALS", Equals = "EQUALS",
NotEquals = "NOT_EQUALS", NotEquals = "NOT_EQUALS",
@@ -285,6 +292,18 @@ export type ConfigureGeneralMutation = {
export type ConfigureGeneralConfigureGeneral = ConfigGeneralDataFragment; export type ConfigureGeneralConfigureGeneral = ConfigGeneralDataFragment;
export type ConfigureInterfaceVariables = {
input: ConfigInterfaceInput;
};
export type ConfigureInterfaceMutation = {
__typename?: "Mutation";
configureInterface: ConfigureInterfaceConfigureInterface;
};
export type ConfigureInterfaceConfigureInterface = ConfigInterfaceDataFragment;
export type PerformerCreateVariables = { export type PerformerCreateVariables = {
name?: Maybe<string>; name?: Maybe<string>;
url?: Maybe<string>; url?: Maybe<string>;
@@ -952,14 +971,26 @@ export type ConfigGeneralDataFragment = {
generatedPath: string; generatedPath: string;
}; };
export type ConfigInterfaceDataFragment = {
__typename?: "ConfigInterfaceResult";
css: Maybe<string>;
cssEnabled: Maybe<boolean>;
};
export type ConfigDataFragment = { export type ConfigDataFragment = {
__typename?: "ConfigResult"; __typename?: "ConfigResult";
general: ConfigDataGeneral; general: ConfigDataGeneral;
interface: ConfigDataInterface;
}; };
export type ConfigDataGeneral = ConfigGeneralDataFragment; export type ConfigDataGeneral = ConfigGeneralDataFragment;
export type ConfigDataInterface = ConfigInterfaceDataFragment;
export type GalleryDataFragment = { export type GalleryDataFragment = {
__typename?: "Gallery"; __typename?: "Gallery";
@@ -1335,14 +1366,25 @@ export const ConfigGeneralDataFragmentDoc = gql`
} }
`; `;
export const ConfigInterfaceDataFragmentDoc = gql`
fragment ConfigInterfaceData on ConfigInterfaceResult {
css
cssEnabled
}
`;
export const ConfigDataFragmentDoc = gql` export const ConfigDataFragmentDoc = gql`
fragment ConfigData on ConfigResult { fragment ConfigData on ConfigResult {
general { general {
...ConfigGeneralData ...ConfigGeneralData
} }
interface {
...ConfigInterfaceData
}
} }
${ConfigGeneralDataFragmentDoc} ${ConfigGeneralDataFragmentDoc}
${ConfigInterfaceDataFragmentDoc}
`; `;
export const SlimPerformerDataFragmentDoc = gql` export const SlimPerformerDataFragmentDoc = gql`
@@ -1574,6 +1616,26 @@ export function useConfigureGeneral(
ConfigureGeneralVariables ConfigureGeneralVariables
>(ConfigureGeneralDocument, baseOptions); >(ConfigureGeneralDocument, baseOptions);
} }
export const ConfigureInterfaceDocument = gql`
mutation ConfigureInterface($input: ConfigInterfaceInput!) {
configureInterface(input: $input) {
...ConfigInterfaceData
}
}
${ConfigInterfaceDataFragmentDoc}
`;
export function useConfigureInterface(
baseOptions?: ReactApolloHooks.MutationHookOptions<
ConfigureInterfaceMutation,
ConfigureInterfaceVariables
>
) {
return ReactApolloHooks.useMutation<
ConfigureInterfaceMutation,
ConfigureInterfaceVariables
>(ConfigureInterfaceDocument, baseOptions);
}
export const PerformerCreateDocument = gql` export const PerformerCreateDocument = gql`
mutation PerformerCreate( mutation PerformerCreate(
$name: String $name: String

View File

@@ -8,11 +8,14 @@ import "./index.scss";
import * as serviceWorker from "./serviceWorker"; import * as serviceWorker from "./serviceWorker";
ReactDOM.render(( ReactDOM.render((
<>
<link rel="stylesheet" type="text/css" href="/css"/>
<BrowserRouter> <BrowserRouter>
<ApolloProvider client={StashService.initialize()!}> <ApolloProvider client={StashService.initialize()!}>
<App /> <App />
</ApolloProvider> </ApolloProvider>
</BrowserRouter> </BrowserRouter>
</>
), document.getElementById("root")); ), document.getElementById("root"));
// If you want your app to work offline and load faster, you can change // If you want your app to work offline and load faster, you can change