mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Default view filters (#4962)
* Merge/adapt from yoshnopa:defaultDetails * Deprecate and remove default filter calls * Fix weird behaviour when clicking set as default * Update deprecated get/set default filter resolvers * Add config migration --------- Co-authored-by: yoshnopa <usingusenet@protonmail.com>
This commit is contained in:
2
go.mod
2
go.mod
@@ -34,6 +34,7 @@ require (
|
|||||||
github.com/knadh/koanf v1.5.0
|
github.com/knadh/koanf v1.5.0
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.22
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
|
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
||||||
github.com/remeh/sizedwaitgroup v1.0.0
|
github.com/remeh/sizedwaitgroup v1.0.0
|
||||||
@@ -88,7 +89,6 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ type Query {
|
|||||||
findSavedFilter(id: ID!): SavedFilter
|
findSavedFilter(id: ID!): SavedFilter
|
||||||
findSavedFilters(mode: FilterMode): [SavedFilter!]!
|
findSavedFilters(mode: FilterMode): [SavedFilter!]!
|
||||||
findDefaultFilter(mode: FilterMode!): SavedFilter
|
findDefaultFilter(mode: FilterMode!): SavedFilter
|
||||||
|
@deprecated(reason: "default filter now stored in UI config")
|
||||||
|
|
||||||
"Find a scene by ID or Checksum"
|
"Find a scene by ID or Checksum"
|
||||||
findScene(id: ID, checksum: String): Scene
|
findScene(id: ID, checksum: String): Scene
|
||||||
@@ -345,6 +346,7 @@ type Mutation {
|
|||||||
saveFilter(input: SaveFilterInput!): SavedFilter!
|
saveFilter(input: SaveFilterInput!): SavedFilter!
|
||||||
destroySavedFilter(input: DestroyFilterInput!): Boolean!
|
destroySavedFilter(input: DestroyFilterInput!): Boolean!
|
||||||
setDefaultFilter(input: SetDefaultFilterInput!): Boolean!
|
setDefaultFilter(input: SetDefaultFilterInput!): Boolean!
|
||||||
|
@deprecated(reason: "now uses UI config")
|
||||||
|
|
||||||
"Change general configuration options"
|
"Change general configuration options"
|
||||||
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
|
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/stashapp/stash/internal/manager/config"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *mutationResolver) SaveFilter(ctx context.Context, input SaveFilterInput) (ret *models.SavedFilter, err error) {
|
func (r *mutationResolver) SaveFilter(ctx context.Context, input SaveFilterInput) (ret *models.SavedFilter, err error) {
|
||||||
@@ -67,30 +70,48 @@ func (r *mutationResolver) DestroySavedFilter(ctx context.Context, input Destroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) SetDefaultFilter(ctx context.Context, input SetDefaultFilterInput) (bool, error) {
|
func (r *mutationResolver) SetDefaultFilter(ctx context.Context, input SetDefaultFilterInput) (bool, error) {
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
// deprecated - write to the config in the meantime
|
||||||
qb := r.repository.SavedFilter
|
config := config.GetInstance()
|
||||||
|
|
||||||
if input.FindFilter == nil && input.ObjectFilter == nil && input.UIOptions == nil {
|
uiConfig := config.GetUIConfiguration()
|
||||||
// clearing
|
if uiConfig == nil {
|
||||||
def, err := qb.FindDefault(ctx, input.Mode)
|
uiConfig = make(map[string]interface{})
|
||||||
if err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if def != nil {
|
m := utils.NestedMap(uiConfig)
|
||||||
return qb.Destroy(ctx, def.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
if input.FindFilter == nil && input.ObjectFilter == nil && input.UIOptions == nil {
|
||||||
|
// clearing
|
||||||
|
m.Delete("defaultFilters." + strings.ToLower(input.Mode.String()))
|
||||||
|
config.SetUIConfiguration(m)
|
||||||
|
|
||||||
|
if err := config.Write(); err != nil {
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return qb.SetDefault(ctx, &models.SavedFilter{
|
return true, nil
|
||||||
Mode: input.Mode,
|
}
|
||||||
FindFilter: input.FindFilter,
|
|
||||||
ObjectFilter: input.ObjectFilter,
|
subMap := make(map[string]interface{})
|
||||||
UIOptions: input.UIOptions,
|
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
})
|
TagName: "json",
|
||||||
}); err != nil {
|
WeaklyTypedInput: true,
|
||||||
|
Result: &subMap,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.Decode(input); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Set("defaultFilters."+strings.ToLower(input.Mode.String()), subMap)
|
||||||
|
|
||||||
|
config.SetUIConfiguration(m)
|
||||||
|
|
||||||
|
if err := config.Write(); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ package api
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/stashapp/stash/internal/manager/config"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *queryResolver) FindSavedFilter(ctx context.Context, id string) (ret *models.SavedFilter, err error) {
|
func (r *queryResolver) FindSavedFilter(ctx context.Context, id string) (ret *models.SavedFilter, err error) {
|
||||||
@@ -37,11 +41,35 @@ func (r *queryResolver) FindSavedFilters(ctx context.Context, mode *models.Filte
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) FindDefaultFilter(ctx context.Context, mode models.FilterMode) (ret *models.SavedFilter, err error) {
|
func (r *queryResolver) FindDefaultFilter(ctx context.Context, mode models.FilterMode) (ret *models.SavedFilter, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
// deprecated - read from the config in the meantime
|
||||||
ret, err = r.repository.SavedFilter.FindDefault(ctx, mode)
|
config := config.GetInstance()
|
||||||
return err
|
|
||||||
}); err != nil {
|
uiConfig := config.GetUIConfiguration()
|
||||||
|
if uiConfig == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m := utils.NestedMap(uiConfig)
|
||||||
|
filterRaw, _ := m.Get("defaultFilters." + strings.ToLower(mode.String()))
|
||||||
|
|
||||||
|
if filterRaw == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = &models.SavedFilter{}
|
||||||
|
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
|
TagName: "json",
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: ret,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ret, err
|
|
||||||
|
if err := d.Decode(filterRaw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,11 @@ type SavedFilterReader interface {
|
|||||||
Find(ctx context.Context, id int) (*SavedFilter, error)
|
Find(ctx context.Context, id int) (*SavedFilter, error)
|
||||||
FindMany(ctx context.Context, ids []int, ignoreNotFound bool) ([]*SavedFilter, error)
|
FindMany(ctx context.Context, ids []int, ignoreNotFound bool) ([]*SavedFilter, error)
|
||||||
FindByMode(ctx context.Context, mode FilterMode) ([]*SavedFilter, error)
|
FindByMode(ctx context.Context, mode FilterMode) ([]*SavedFilter, error)
|
||||||
FindDefault(ctx context.Context, mode FilterMode) (*SavedFilter, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedFilterWriter interface {
|
type SavedFilterWriter interface {
|
||||||
Create(ctx context.Context, obj *SavedFilter) error
|
Create(ctx context.Context, obj *SavedFilter) error
|
||||||
Update(ctx context.Context, obj *SavedFilter) error
|
Update(ctx context.Context, obj *SavedFilter) error
|
||||||
SetDefault(ctx context.Context, obj *SavedFilter) error
|
|
||||||
Destroy(ctx context.Context, id int) error
|
Destroy(ctx context.Context, id int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const (
|
|||||||
dbConnTimeout = 30
|
dbConnTimeout = 30
|
||||||
)
|
)
|
||||||
|
|
||||||
var appSchemaVersion uint = 59
|
var appSchemaVersion uint = 60
|
||||||
|
|
||||||
//go:embed migrations/*.sql
|
//go:embed migrations/*.sql
|
||||||
var migrationsBox embed.FS
|
var migrationsBox embed.FS
|
||||||
|
|||||||
2
pkg/sqlite/migrations/60_default_filter_move.up.sql
Normal file
2
pkg/sqlite/migrations/60_default_filter_move.up.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- no schema changes
|
||||||
|
-- default filters will be removed in post-migration
|
||||||
176
pkg/sqlite/migrations/60_postmigrate.go
Normal file
176
pkg/sqlite/migrations/60_postmigrate.go
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/stashapp/stash/internal/manager/config"
|
||||||
|
"github.com/stashapp/stash/pkg/logger"
|
||||||
|
"github.com/stashapp/stash/pkg/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type schema60Migrator struct {
|
||||||
|
migrator
|
||||||
|
}
|
||||||
|
|
||||||
|
func post60(ctx context.Context, db *sqlx.DB) error {
|
||||||
|
logger.Info("Running post-migration for schema version 60")
|
||||||
|
|
||||||
|
m := schema60Migrator{
|
||||||
|
migrator: migrator{
|
||||||
|
db: db,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.migrate(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *schema60Migrator) decodeJSON(s string, v interface{}) {
|
||||||
|
if s == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(s), v); err != nil {
|
||||||
|
logger.Errorf("error decoding json %q: %v", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type schema60DefaultFilters map[string]interface{}
|
||||||
|
|
||||||
|
func (m *schema60Migrator) migrate(ctx context.Context) error {
|
||||||
|
|
||||||
|
// save default filters into the UI config
|
||||||
|
if err := m.withTxn(ctx, func(tx *sqlx.Tx) error {
|
||||||
|
query := "SELECT id, mode, find_filter, object_filter, ui_options FROM `saved_filters` WHERE `name` = ''"
|
||||||
|
|
||||||
|
rows, err := m.db.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
defaultFilters := make(schema60DefaultFilters)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
id int
|
||||||
|
mode string
|
||||||
|
findFilterStr string
|
||||||
|
objectFilterStr string
|
||||||
|
uiOptionsStr string
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := rows.Scan(&id, &mode, &findFilterStr, &objectFilterStr, &uiOptionsStr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the filters to the correct format
|
||||||
|
findFilter := make(map[string]interface{})
|
||||||
|
objectFilter := make(map[string]interface{})
|
||||||
|
uiOptions := make(map[string]interface{})
|
||||||
|
|
||||||
|
m.decodeJSON(findFilterStr, &findFilter)
|
||||||
|
m.decodeJSON(objectFilterStr, &objectFilter)
|
||||||
|
m.decodeJSON(uiOptionsStr, &uiOptions)
|
||||||
|
|
||||||
|
o := map[string]interface{}{
|
||||||
|
"mode": mode,
|
||||||
|
"find_filter": findFilter,
|
||||||
|
"object_filter": objectFilter,
|
||||||
|
"ui_options": uiOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultFilters[strings.ToLower(mode)] = o
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.saveDefaultFilters(defaultFilters); err != nil {
|
||||||
|
return fmt.Errorf("saving default filters: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the default filters from the database
|
||||||
|
query = "DELETE FROM `saved_filters` WHERE `name` = ''"
|
||||||
|
if _, err := m.db.Exec(query); err != nil {
|
||||||
|
return fmt.Errorf("deleting default filters: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *schema60Migrator) saveDefaultFilters(defaultFilters schema60DefaultFilters) error {
|
||||||
|
if len(defaultFilters) == 0 {
|
||||||
|
logger.Debugf("no default filters to save")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the default filters into the UI config
|
||||||
|
config := config.GetInstance()
|
||||||
|
|
||||||
|
orgPath := config.GetConfigFile()
|
||||||
|
|
||||||
|
if orgPath == "" {
|
||||||
|
// no config file to migrate (usually in a test or new system)
|
||||||
|
logger.Debugf("no config file to migrate")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uiConfig := config.GetUIConfiguration()
|
||||||
|
if uiConfig == nil {
|
||||||
|
uiConfig = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the defaultFilters key already exists, don't overwrite them
|
||||||
|
if _, found := uiConfig["defaultFilters"]; found {
|
||||||
|
logger.Warn("defaultFilters already exists in the UI config, skipping migration")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.backupConfig(orgPath); err != nil {
|
||||||
|
return fmt.Errorf("backing up config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uiConfig["defaultFilters"] = map[string]interface{}(defaultFilters)
|
||||||
|
config.SetUIConfiguration(uiConfig)
|
||||||
|
|
||||||
|
if err := config.Write(); err != nil {
|
||||||
|
return fmt.Errorf("failed to write config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *schema60Migrator) backupConfig(orgPath string) error {
|
||||||
|
c := config.GetInstance()
|
||||||
|
|
||||||
|
// save a backup of the original config file
|
||||||
|
backupPath := fmt.Sprintf("%s.59.%s", orgPath, time.Now().Format("20060102_150405"))
|
||||||
|
|
||||||
|
data, err := c.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal backup config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("Backing up config to %s", backupPath)
|
||||||
|
if err := os.WriteFile(backupPath, data, 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write backup config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sqlite.RegisterPostMigration(60, post60)
|
||||||
|
}
|
||||||
@@ -141,23 +141,6 @@ func (qb *SavedFilterStore) Update(ctx context.Context, updatedObject *models.Sa
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *SavedFilterStore) SetDefault(ctx context.Context, obj *models.SavedFilter) error {
|
|
||||||
// find the existing default
|
|
||||||
existing, err := qb.FindDefault(ctx, obj.Mode)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj.Name = savedFilterDefaultName
|
|
||||||
|
|
||||||
if existing != nil {
|
|
||||||
obj.ID = existing.ID
|
|
||||||
return qb.Update(ctx, obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
return qb.Create(ctx, obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qb *SavedFilterStore) Destroy(ctx context.Context, id int) error {
|
func (qb *SavedFilterStore) Destroy(ctx context.Context, id int) error {
|
||||||
return qb.destroyExisting(ctx, []int{id})
|
return qb.destroyExisting(ctx, []int{id})
|
||||||
}
|
}
|
||||||
@@ -258,22 +241,6 @@ func (qb *SavedFilterStore) FindByMode(ctx context.Context, mode models.FilterMo
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *SavedFilterStore) FindDefault(ctx context.Context, mode models.FilterMode) (*models.SavedFilter, error) {
|
|
||||||
// SELECT * FROM saved_filters WHERE mode = ? AND name = ?
|
|
||||||
table := qb.table()
|
|
||||||
sq := qb.selectDataset().Prepared(true).Where(
|
|
||||||
table.Col("mode").Eq(mode),
|
|
||||||
table.Col("name").Eq(savedFilterDefaultName),
|
|
||||||
)
|
|
||||||
|
|
||||||
ret, err := qb.get(ctx, sq)
|
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qb *SavedFilterStore) All(ctx context.Context) ([]*models.SavedFilter, error) {
|
func (qb *SavedFilterStore) All(ctx context.Context) ([]*models.SavedFilter, error) {
|
||||||
return qb.getMany(ctx, qb.selectDataset())
|
return qb.getMany(ctx, qb.selectDataset())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,66 +96,6 @@ func TestSavedFilterDestroy(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSavedFilterFindDefault(t *testing.T) {
|
|
||||||
withTxn(func(ctx context.Context) error {
|
|
||||||
def, err := db.SavedFilter.FindDefault(ctx, models.FilterModeScenes)
|
|
||||||
if err == nil {
|
|
||||||
assert.Equal(t, savedFilterIDs[savedFilterIdxDefaultScene], def.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSavedFilterSetDefault(t *testing.T) {
|
|
||||||
filterQ := ""
|
|
||||||
filterPage := 1
|
|
||||||
filterPerPage := 40
|
|
||||||
filterSort := "date"
|
|
||||||
filterDirection := models.SortDirectionEnumAsc
|
|
||||||
findFilter := models.FindFilterType{
|
|
||||||
Q: &filterQ,
|
|
||||||
Page: &filterPage,
|
|
||||||
PerPage: &filterPerPage,
|
|
||||||
Sort: &filterSort,
|
|
||||||
Direction: &filterDirection,
|
|
||||||
}
|
|
||||||
objectFilter := map[string]interface{}{
|
|
||||||
"test": "foo",
|
|
||||||
}
|
|
||||||
uiOptions := map[string]interface{}{
|
|
||||||
"display_mode": 1,
|
|
||||||
"zoom_index": 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
withTxn(func(ctx context.Context) error {
|
|
||||||
err := db.SavedFilter.SetDefault(ctx, &models.SavedFilter{
|
|
||||||
Mode: models.FilterModeMovies,
|
|
||||||
FindFilter: &findFilter,
|
|
||||||
ObjectFilter: objectFilter,
|
|
||||||
UIOptions: uiOptions,
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
var defID int
|
|
||||||
withTxn(func(ctx context.Context) error {
|
|
||||||
def, err := db.SavedFilter.FindDefault(ctx, models.FilterModeMovies)
|
|
||||||
if err == nil {
|
|
||||||
defID = def.ID
|
|
||||||
assert.Equal(t, &findFilter, def.FindFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
// destroy it again
|
|
||||||
withTxn(func(ctx context.Context) error {
|
|
||||||
return db.SavedFilter.Destroy(ctx, defID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Update
|
// TODO Update
|
||||||
// TODO Destroy
|
// TODO Destroy
|
||||||
// TODO Find
|
// TODO Find
|
||||||
|
|||||||
@@ -16,11 +16,6 @@ extend input SaveFilterInput {
|
|||||||
ui_options: SavedUIOptions
|
ui_options: SavedUIOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
extend input SetDefaultFilterInput {
|
|
||||||
object_filter: SavedObjectFilter
|
|
||||||
ui_options: SavedUIOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
configureUI(input: Map, partial: Map): UIConfig!
|
configureUI(input: Map, partial: Map): UIConfig!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,3 @@ mutation SaveFilter($input: SaveFilterInput!) {
|
|||||||
mutation DestroySavedFilter($input: DestroyFilterInput!) {
|
mutation DestroySavedFilter($input: DestroyFilterInput!) {
|
||||||
destroySavedFilter(input: $input)
|
destroySavedFilter(input: $input)
|
||||||
}
|
}
|
||||||
|
|
||||||
mutation SetDefaultFilter($input: SetDefaultFilterInput!) {
|
|
||||||
setDefaultFilter(input: $input)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,9 +9,3 @@ query FindSavedFilters($mode: FilterMode) {
|
|||||||
...SavedFilterData
|
...SavedFilterData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query FindDefaultFilter($mode: FilterMode!) {
|
|
||||||
findDefaultFilter(mode: $mode) {
|
|
||||||
...SavedFilterData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ import React from "react";
|
|||||||
import { Route, Switch } from "react-router-dom";
|
import { Route, Switch } from "react-router-dom";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTitleProps } from "src/hooks/title";
|
import { useTitleProps } from "src/hooks/title";
|
||||||
import { PersistanceLevel } from "../List/ItemList";
|
|
||||||
import Gallery from "./GalleryDetails/Gallery";
|
import Gallery from "./GalleryDetails/Gallery";
|
||||||
import GalleryCreate from "./GalleryDetails/GalleryCreate";
|
import GalleryCreate from "./GalleryDetails/GalleryCreate";
|
||||||
import { GalleryList } from "./GalleryList";
|
import { GalleryList } from "./GalleryList";
|
||||||
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const Galleries: React.FC = () => {
|
const Galleries: React.FC = () => {
|
||||||
useScrollToTopOnMount();
|
useScrollToTopOnMount();
|
||||||
|
|
||||||
return <GalleryList persistState={PersistanceLevel.ALL} />;
|
return <GalleryList view={View.Galleries} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GalleryRoutes: React.FC = () => {
|
const GalleryRoutes: React.FC = () => {
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ import { GalleriesCriterion } from "src/models/list-filter/criteria/galleries";
|
|||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { ImageList } from "src/components/Images/ImageList";
|
import { ImageList } from "src/components/Images/ImageList";
|
||||||
import { mutateRemoveGalleryImages } from "src/core/StashService";
|
import { mutateRemoveGalleryImages } from "src/core/StashService";
|
||||||
import {
|
import { showWhenSelected } from "src/components/List/ItemList";
|
||||||
showWhenSelected,
|
|
||||||
PersistanceLevel,
|
|
||||||
} from "src/components/List/ItemList";
|
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { faMinus } from "@fortawesome/free-solid-svg-icons";
|
import { faMinus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { galleryTitle } from "src/core/galleries";
|
import { galleryTitle } from "src/core/galleries";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IGalleryDetailsProps {
|
interface IGalleryDetailsProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -102,8 +100,7 @@ export const GalleryImagesPanel: React.FC<IGalleryDetailsProps> = ({
|
|||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
alterQuery={active}
|
alterQuery={active}
|
||||||
extraOperations={otherOperations}
|
extraOperations={otherOperations}
|
||||||
persistState={PersistanceLevel.VIEW}
|
view={View.GalleryImages}
|
||||||
persistanceKey="galleryimages"
|
|
||||||
chapters={gallery.chapters}
|
chapters={gallery.chapters}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ import cloneDeep from "lodash-es/cloneDeep";
|
|||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import { makeItemList, showWhenSelected } from "../List/ItemList";
|
||||||
makeItemList,
|
|
||||||
PersistanceLevel,
|
|
||||||
showWhenSelected,
|
|
||||||
} from "../List/ItemList";
|
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { DisplayMode } from "src/models/list-filter/types";
|
import { DisplayMode } from "src/models/list-filter/types";
|
||||||
import { queryFindGalleries, useFindGalleries } from "src/core/StashService";
|
import { queryFindGalleries, useFindGalleries } from "src/core/StashService";
|
||||||
@@ -18,6 +14,7 @@ import { DeleteGalleriesDialog } from "./DeleteGalleriesDialog";
|
|||||||
import { ExportDialog } from "../Shared/ExportDialog";
|
import { ExportDialog } from "../Shared/ExportDialog";
|
||||||
import { GalleryListTable } from "./GalleryListTable";
|
import { GalleryListTable } from "./GalleryListTable";
|
||||||
import { GalleryCardGrid } from "./GalleryGridCard";
|
import { GalleryCardGrid } from "./GalleryGridCard";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const GalleryItemList = makeItemList({
|
const GalleryItemList = makeItemList({
|
||||||
filterMode: GQL.FilterMode.Galleries,
|
filterMode: GQL.FilterMode.Galleries,
|
||||||
@@ -32,13 +29,13 @@ const GalleryItemList = makeItemList({
|
|||||||
|
|
||||||
interface IGalleryList {
|
interface IGalleryList {
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||||
persistState?: PersistanceLevel;
|
view?: View;
|
||||||
alterQuery?: boolean;
|
alterQuery?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GalleryList: React.FC<IGalleryList> = ({
|
export const GalleryList: React.FC<IGalleryList> = ({
|
||||||
filterHook,
|
filterHook,
|
||||||
persistState,
|
view,
|
||||||
alterQuery,
|
alterQuery,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -192,7 +189,7 @@ export const GalleryList: React.FC<IGalleryList> = ({
|
|||||||
zoomable
|
zoomable
|
||||||
selectable
|
selectable
|
||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
persistState={persistState}
|
view={view}
|
||||||
alterQuery={alterQuery}
|
alterQuery={alterQuery}
|
||||||
otherOperations={otherOperations}
|
otherOperations={otherOperations}
|
||||||
addKeybinds={addKeybinds}
|
addKeybinds={addKeybinds}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { queryFindImages, useFindImages } from "src/core/StashService";
|
|||||||
import {
|
import {
|
||||||
makeItemList,
|
makeItemList,
|
||||||
IItemListOperation,
|
IItemListOperation,
|
||||||
PersistanceLevel,
|
|
||||||
showWhenSelected,
|
showWhenSelected,
|
||||||
} from "../List/ItemList";
|
} from "../List/ItemList";
|
||||||
import { useLightbox } from "src/hooks/Lightbox/hooks";
|
import { useLightbox } from "src/hooks/Lightbox/hooks";
|
||||||
@@ -31,6 +30,7 @@ import { objectTitle } from "src/core/files";
|
|||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { ImageGridCard } from "./ImageGridCard";
|
import { ImageGridCard } from "./ImageGridCard";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
interface IImageWallProps {
|
interface IImageWallProps {
|
||||||
images: GQL.SlimImageDataFragment[];
|
images: GQL.SlimImageDataFragment[];
|
||||||
@@ -270,8 +270,7 @@ const ImageItemList = makeItemList({
|
|||||||
|
|
||||||
interface IImageList {
|
interface IImageList {
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||||
persistState?: PersistanceLevel;
|
view?: View;
|
||||||
persistanceKey?: string;
|
|
||||||
alterQuery?: boolean;
|
alterQuery?: boolean;
|
||||||
extraOperations?: IItemListOperation<GQL.FindImagesQueryResult>[];
|
extraOperations?: IItemListOperation<GQL.FindImagesQueryResult>[];
|
||||||
chapters?: GQL.GalleryChapterDataFragment[];
|
chapters?: GQL.GalleryChapterDataFragment[];
|
||||||
@@ -279,8 +278,7 @@ interface IImageList {
|
|||||||
|
|
||||||
export const ImageList: React.FC<IImageList> = ({
|
export const ImageList: React.FC<IImageList> = ({
|
||||||
filterHook,
|
filterHook,
|
||||||
persistState,
|
view,
|
||||||
persistanceKey,
|
|
||||||
alterQuery,
|
alterQuery,
|
||||||
extraOperations,
|
extraOperations,
|
||||||
chapters = [],
|
chapters = [],
|
||||||
@@ -421,8 +419,7 @@ export const ImageList: React.FC<IImageList> = ({
|
|||||||
zoomable
|
zoomable
|
||||||
selectable
|
selectable
|
||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
persistState={persistState}
|
view={view}
|
||||||
persistanceKey={persistanceKey}
|
|
||||||
alterQuery={alterQuery}
|
alterQuery={alterQuery}
|
||||||
otherOperations={otherOperations}
|
otherOperations={otherOperations}
|
||||||
addKeybinds={addKeybinds}
|
addKeybinds={addKeybinds}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import React from "react";
|
|||||||
import { Route, Switch } from "react-router-dom";
|
import { Route, Switch } from "react-router-dom";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTitleProps } from "src/hooks/title";
|
import { useTitleProps } from "src/hooks/title";
|
||||||
import { PersistanceLevel } from "../List/ItemList";
|
|
||||||
import Image from "./ImageDetails/Image";
|
import Image from "./ImageDetails/Image";
|
||||||
import { ImageList } from "./ImageList";
|
import { ImageList } from "./ImageList";
|
||||||
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const Images: React.FC = () => {
|
const Images: React.FC = () => {
|
||||||
useScrollToTopOnMount();
|
useScrollToTopOnMount();
|
||||||
|
|
||||||
return <ImageList persistState={PersistanceLevel.ALL} />;
|
return <ImageList view={View.Images} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ImageRoutes: React.FC = () => {
|
const ImageRoutes: React.FC = () => {
|
||||||
|
|||||||
@@ -19,11 +19,9 @@ import {
|
|||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||||
import { useInterfaceLocalForage } from "src/hooks/LocalForage";
|
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { getFilterOptions } from "src/models/list-filter/factory";
|
import { getFilterOptions } from "src/models/list-filter/factory";
|
||||||
import { useFindDefaultFilter } from "src/core/StashService";
|
|
||||||
import { Pagination, PaginationIndex } from "./Pagination";
|
import { Pagination, PaginationIndex } from "./Pagination";
|
||||||
import { EditFilterDialog } from "src/components/List/EditFilterDialog";
|
import { EditFilterDialog } from "src/components/List/EditFilterDialog";
|
||||||
import { ListFilter } from "./ListFilter";
|
import { ListFilter } from "./ListFilter";
|
||||||
@@ -33,15 +31,8 @@ import { ListOperationButtons } from "./ListOperationButtons";
|
|||||||
import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
||||||
import { DisplayMode } from "src/models/list-filter/types";
|
import { DisplayMode } from "src/models/list-filter/types";
|
||||||
import { ButtonToolbar } from "react-bootstrap";
|
import { ButtonToolbar } from "react-bootstrap";
|
||||||
|
import { View } from "./views";
|
||||||
export enum PersistanceLevel {
|
import { useDefaultFilter } from "./util";
|
||||||
// do not load default query or persist display mode
|
|
||||||
NONE,
|
|
||||||
// load default query, don't load or persist display mode
|
|
||||||
ALL,
|
|
||||||
// load and persist display mode only
|
|
||||||
VIEW,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IDataItem {
|
interface IDataItem {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -79,8 +70,7 @@ interface IRenderListProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IItemListProps<T extends QueryResult, E extends IDataItem> {
|
interface IItemListProps<T extends QueryResult, E extends IDataItem> {
|
||||||
persistState?: PersistanceLevel;
|
view?: View;
|
||||||
persistanceKey?: string;
|
|
||||||
defaultSort?: string;
|
defaultSort?: string;
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||||
filterDialog?: (
|
filterDialog?: (
|
||||||
@@ -140,7 +130,7 @@ export function makeItemList<T extends QueryResult, E extends IDataItem>({
|
|||||||
filterHook,
|
filterHook,
|
||||||
onChangePage: _onChangePage,
|
onChangePage: _onChangePage,
|
||||||
updateFilter,
|
updateFilter,
|
||||||
persistState,
|
view,
|
||||||
zoomable,
|
zoomable,
|
||||||
selectable,
|
selectable,
|
||||||
otherOperations,
|
otherOperations,
|
||||||
@@ -480,7 +470,7 @@ export function makeItemList<T extends QueryResult, E extends IDataItem>({
|
|||||||
filter={filter}
|
filter={filter}
|
||||||
filterOptions={filterOptions}
|
filterOptions={filterOptions}
|
||||||
openFilterDialog={() => setShowEditFilter(true)}
|
openFilterDialog={() => setShowEditFilter(true)}
|
||||||
persistState={persistState}
|
view={view}
|
||||||
/>
|
/>
|
||||||
<ListOperationButtons
|
<ListOperationButtons
|
||||||
onSelectAll={selectable ? onSelectAll : undefined}
|
onSelectAll={selectable ? onSelectAll : undefined}
|
||||||
@@ -531,8 +521,7 @@ export function makeItemList<T extends QueryResult, E extends IDataItem>({
|
|||||||
|
|
||||||
const ItemList: React.FC<IItemListProps<T, E>> = (props) => {
|
const ItemList: React.FC<IItemListProps<T, E>> = (props) => {
|
||||||
const {
|
const {
|
||||||
persistState,
|
view,
|
||||||
persistanceKey = filterMode,
|
|
||||||
defaultSort = filterOptions.defaultSortBy,
|
defaultSort = filterOptions.defaultSortBy,
|
||||||
defaultZoomIndex,
|
defaultZoomIndex,
|
||||||
alterQuery = true,
|
alterQuery = true,
|
||||||
@@ -540,7 +529,6 @@ export function makeItemList<T extends QueryResult, E extends IDataItem>({
|
|||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [interfaceState, setInterfaceState] = useInterfaceLocalForage();
|
|
||||||
const [filterInitialised, setFilterInitialised] = useState(false);
|
const [filterInitialised, setFilterInitialised] = useState(false);
|
||||||
const { configuration: config } = useContext(ConfigurationContext);
|
const { configuration: config } = useContext(ConfigurationContext);
|
||||||
|
|
||||||
@@ -550,35 +538,11 @@ export function makeItemList<T extends QueryResult, E extends IDataItem>({
|
|||||||
() => new ListFilterModel(filterMode)
|
() => new ListFilterModel(filterMode)
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateSavedFilter = useCallback(
|
const { defaultFilter, loading: defaultFilterLoading } = useDefaultFilter(
|
||||||
(updatedFilter: ListFilterModel) => {
|
filterMode,
|
||||||
setInterfaceState((prevState) => {
|
view
|
||||||
if (!prevState.queryConfig) {
|
|
||||||
prevState.queryConfig = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldFilter = prevState.queryConfig[persistanceKey]?.filter ?? "";
|
|
||||||
const newFilter = new URLSearchParams(oldFilter);
|
|
||||||
newFilter.set("disp", String(updatedFilter.displayMode));
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
queryConfig: {
|
|
||||||
...prevState.queryConfig,
|
|
||||||
[persistanceKey]: {
|
|
||||||
...prevState.queryConfig[persistanceKey],
|
|
||||||
filter: newFilter.toString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[persistanceKey, setInterfaceState]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: defaultFilter, loading: defaultFilterLoading } =
|
|
||||||
useFindDefaultFilter(filterMode);
|
|
||||||
|
|
||||||
const updateQueryParams = useCallback(
|
const updateQueryParams = useCallback(
|
||||||
(newFilter: ListFilterModel) => {
|
(newFilter: ListFilterModel) => {
|
||||||
if (!alterQuery) return;
|
if (!alterQuery) return;
|
||||||
@@ -593,11 +557,8 @@ export function makeItemList<T extends QueryResult, E extends IDataItem>({
|
|||||||
(newFilter: ListFilterModel) => {
|
(newFilter: ListFilterModel) => {
|
||||||
setFilter(newFilter);
|
setFilter(newFilter);
|
||||||
updateQueryParams(newFilter);
|
updateQueryParams(newFilter);
|
||||||
if (persistState === PersistanceLevel.VIEW) {
|
|
||||||
updateSavedFilter(newFilter);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[persistState, updateSavedFilter, updateQueryParams]
|
[updateQueryParams]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 'Startup' hook, initialises the filters
|
// 'Startup' hook, initialises the filters
|
||||||
@@ -605,53 +566,28 @@ export function makeItemList<T extends QueryResult, E extends IDataItem>({
|
|||||||
// Only run once
|
// Only run once
|
||||||
if (filterInitialised) return;
|
if (filterInitialised) return;
|
||||||
|
|
||||||
let newFilter = new ListFilterModel(
|
let newFilter = new ListFilterModel(filterMode, config, defaultZoomIndex);
|
||||||
filterMode,
|
|
||||||
config,
|
|
||||||
defaultSort,
|
|
||||||
defaultDisplayMode,
|
|
||||||
defaultZoomIndex
|
|
||||||
);
|
|
||||||
let loadDefault = true;
|
let loadDefault = true;
|
||||||
if (alterQuery && location.search) {
|
if (alterQuery && location.search) {
|
||||||
loadDefault = false;
|
loadDefault = false;
|
||||||
newFilter.configureFromQueryString(location.search);
|
newFilter.configureFromQueryString(location.search);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (persistState === PersistanceLevel.ALL) {
|
if (view) {
|
||||||
// only set default filter if uninitialised
|
// only set default filter if uninitialised
|
||||||
if (loadDefault) {
|
if (loadDefault) {
|
||||||
// wait until default filter is loaded
|
// wait until default filter is loaded
|
||||||
if (defaultFilterLoading) return;
|
if (defaultFilterLoading) return;
|
||||||
|
|
||||||
if (defaultFilter?.findDefaultFilter) {
|
if (defaultFilter) {
|
||||||
newFilter.currentPage = 1;
|
newFilter = defaultFilter.clone();
|
||||||
try {
|
|
||||||
newFilter.configureFromSavedFilter(
|
|
||||||
defaultFilter.findDefaultFilter
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
// #1507 - reset random seed when loaded
|
// #1507 - reset random seed when loaded
|
||||||
newFilter.randomSeed = -1;
|
newFilter.randomSeed = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (persistState === PersistanceLevel.VIEW) {
|
|
||||||
// wait until forage is initialised
|
|
||||||
if (interfaceState.loading) return;
|
|
||||||
|
|
||||||
const storedQuery = interfaceState.data?.queryConfig?.[persistanceKey];
|
|
||||||
if (persistState === PersistanceLevel.VIEW && storedQuery) {
|
|
||||||
const displayMode = new URLSearchParams(storedQuery.filter).get(
|
|
||||||
"disp"
|
|
||||||
);
|
|
||||||
if (displayMode) {
|
|
||||||
newFilter.displayMode = Number.parseInt(displayMode, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilter(newFilter);
|
setFilter(newFilter);
|
||||||
updateQueryParams(newFilter);
|
updateQueryParams(newFilter);
|
||||||
|
|
||||||
@@ -664,12 +600,10 @@ export function makeItemList<T extends QueryResult, E extends IDataItem>({
|
|||||||
defaultDisplayMode,
|
defaultDisplayMode,
|
||||||
defaultZoomIndex,
|
defaultZoomIndex,
|
||||||
alterQuery,
|
alterQuery,
|
||||||
persistState,
|
view,
|
||||||
updateQueryParams,
|
updateQueryParams,
|
||||||
defaultFilter,
|
defaultFilter,
|
||||||
defaultFilterLoading,
|
defaultFilterLoading,
|
||||||
interfaceState,
|
|
||||||
persistanceKey,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// This hook runs on every page location change (ie navigation),
|
// This hook runs on every page location change (ie navigation),
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import cloneDeep from "lodash-es/cloneDeep";
|
import cloneDeep from "lodash-es/cloneDeep";
|
||||||
import React, {
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
HTMLAttributes,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import { SortDirectionEnum } from "src/core/generated-graphql";
|
import { SortDirectionEnum } from "src/core/generated-graphql";
|
||||||
@@ -27,10 +21,8 @@ import { ListFilterModel } from "src/models/list-filter/filter";
|
|||||||
import useFocus from "src/utils/focus";
|
import useFocus from "src/utils/focus";
|
||||||
import { ListFilterOptions } from "src/models/list-filter/filter-options";
|
import { ListFilterOptions } from "src/models/list-filter/filter-options";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { PersistanceLevel } from "./ItemList";
|
import { SavedFilterDropdown } from "./SavedFilterList";
|
||||||
import { SavedFilterList } from "./SavedFilterList";
|
|
||||||
import {
|
import {
|
||||||
faBookmark,
|
|
||||||
faCaretDown,
|
faCaretDown,
|
||||||
faCaretUp,
|
faCaretUp,
|
||||||
faCheck,
|
faCheck,
|
||||||
@@ -39,12 +31,13 @@ import {
|
|||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FilterButton } from "./Filters/FilterButton";
|
import { FilterButton } from "./Filters/FilterButton";
|
||||||
import { useDebounce } from "src/hooks/debounce";
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
|
import { View } from "./views";
|
||||||
|
|
||||||
interface IListFilterProps {
|
interface IListFilterProps {
|
||||||
onFilterUpdate: (newFilter: ListFilterModel) => void;
|
onFilterUpdate: (newFilter: ListFilterModel) => void;
|
||||||
filter: ListFilterModel;
|
filter: ListFilterModel;
|
||||||
filterOptions: ListFilterOptions;
|
filterOptions: ListFilterOptions;
|
||||||
persistState?: PersistanceLevel;
|
view?: View;
|
||||||
openFilterDialog: () => void;
|
openFilterDialog: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +48,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||||||
filter,
|
filter,
|
||||||
filterOptions,
|
filterOptions,
|
||||||
openFilterDialog,
|
openFilterDialog,
|
||||||
persistState,
|
view,
|
||||||
}) => {
|
}) => {
|
||||||
const [customPageSizeShowing, setCustomPageSizeShowing] = useState(false);
|
const [customPageSizeShowing, setCustomPageSizeShowing] = useState(false);
|
||||||
const [queryRef, setQueryFocus] = useFocus();
|
const [queryRef, setQueryFocus] = useFocus();
|
||||||
@@ -191,22 +184,6 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
const SavedFilterDropdown = React.forwardRef<
|
|
||||||
HTMLDivElement,
|
|
||||||
HTMLAttributes<HTMLDivElement>
|
|
||||||
>(({ style, className }: HTMLAttributes<HTMLDivElement>, ref) => (
|
|
||||||
<div ref={ref} style={style} className={className}>
|
|
||||||
<SavedFilterList
|
|
||||||
filter={filter}
|
|
||||||
onSetFilter={(f) => {
|
|
||||||
onFilterUpdate(f);
|
|
||||||
}}
|
|
||||||
persistState={persistState}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
SavedFilterDropdown.displayName = "SavedFilterDropdown";
|
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
const currentSortBy = filterOptions.sortByOptions.find(
|
const currentSortBy = filterOptions.sortByOptions.find(
|
||||||
(o) => o.value === filter.sortBy
|
(o) => o.value === filter.sortBy
|
||||||
@@ -257,24 +234,13 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ButtonGroup className="mr-2 mb-2">
|
<ButtonGroup className="mr-2 mb-2">
|
||||||
<Dropdown>
|
<SavedFilterDropdown
|
||||||
<OverlayTrigger
|
filter={filter}
|
||||||
placement="top"
|
onSetFilter={(f) => {
|
||||||
overlay={
|
onFilterUpdate(f);
|
||||||
<Tooltip id="filter-tooltip">
|
}}
|
||||||
<FormattedMessage id="search_filter.saved_filters" />
|
view={view}
|
||||||
</Tooltip>
|
/>
|
||||||
}
|
|
||||||
>
|
|
||||||
<Dropdown.Toggle variant="secondary">
|
|
||||||
<Icon icon={faBookmark} />
|
|
||||||
</Dropdown.Toggle>
|
|
||||||
</OverlayTrigger>
|
|
||||||
<Dropdown.Menu
|
|
||||||
as={SavedFilterDropdown}
|
|
||||||
className="saved-filter-list-menu"
|
|
||||||
/>
|
|
||||||
</Dropdown>
|
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
placement="top"
|
placement="top"
|
||||||
overlay={
|
overlay={
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { HTMLAttributes, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
@@ -10,30 +10,30 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from "react-bootstrap";
|
} from "react-bootstrap";
|
||||||
import {
|
import {
|
||||||
|
useConfigureUI,
|
||||||
useFindSavedFilters,
|
useFindSavedFilters,
|
||||||
useSavedFilterDestroy,
|
useSavedFilterDestroy,
|
||||||
useSaveFilter,
|
useSaveFilter,
|
||||||
useSetDefaultFilter,
|
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { SavedFilterDataFragment } from "src/core/generated-graphql";
|
import { SavedFilterDataFragment } from "src/core/generated-graphql";
|
||||||
import { PersistanceLevel } from "./ItemList";
|
import { View } from "./views";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
||||||
import { faSave, faTimes } from "@fortawesome/free-solid-svg-icons";
|
import { faBookmark, faSave, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
interface ISavedFilterListProps {
|
interface ISavedFilterListProps {
|
||||||
filter: ListFilterModel;
|
filter: ListFilterModel;
|
||||||
onSetFilter: (f: ListFilterModel) => void;
|
onSetFilter: (f: ListFilterModel) => void;
|
||||||
persistState?: PersistanceLevel;
|
view?: View;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
||||||
filter,
|
filter,
|
||||||
onSetFilter,
|
onSetFilter,
|
||||||
persistState,
|
view,
|
||||||
}) => {
|
}) => {
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -51,7 +51,7 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
|||||||
|
|
||||||
const [saveFilter] = useSaveFilter();
|
const [saveFilter] = useSaveFilter();
|
||||||
const [destroyFilter] = useSavedFilterDestroy();
|
const [destroyFilter] = useSavedFilterDestroy();
|
||||||
const [setDefaultFilter] = useSetDefaultFilter();
|
const [saveUI] = useConfigureUI();
|
||||||
|
|
||||||
const savedFilters = data?.findSavedFilters ?? [];
|
const savedFilters = data?.findSavedFilters ?? [];
|
||||||
|
|
||||||
@@ -127,18 +127,26 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onSetDefaultFilter() {
|
async function onSetDefaultFilter() {
|
||||||
|
if (!view) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const filterCopy = filter.clone();
|
const filterCopy = filter.clone();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
|
|
||||||
await setDefaultFilter({
|
await saveUI({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
partial: {
|
||||||
mode: filter.mode,
|
defaultFilters: {
|
||||||
find_filter: filterCopy.makeFindFilter(),
|
[view.toString()]: {
|
||||||
object_filter: filterCopy.makeSavedFilter(),
|
mode: filter.mode,
|
||||||
ui_options: filterCopy.makeSavedUIOptions(),
|
find_filter: filterCopy.makeFindFilter(),
|
||||||
|
object_filter: filterCopy.makeSavedFilter(),
|
||||||
|
ui_options: filterCopy.makeSavedUIOptions(),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -302,17 +310,19 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderSetDefaultButton() {
|
function maybeRenderSetDefaultButton() {
|
||||||
if (persistState === PersistanceLevel.ALL) {
|
if (view) {
|
||||||
return (
|
return (
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<Button
|
<Dropdown.Item
|
||||||
|
as={Button}
|
||||||
|
title={intl.formatMessage({ id: "actions.set_as_default" })}
|
||||||
className="set-as-default-button"
|
className="set-as-default-button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => onSetDefaultFilter()}
|
onClick={() => onSetDefaultFilter()}
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ id: "actions.set_as_default" })}
|
{intl.formatMessage({ id: "actions.set_as_default" })}
|
||||||
</Button>
|
</Dropdown.Item>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -357,3 +367,36 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SavedFilterDropdown: React.FC<ISavedFilterListProps> = (props) => {
|
||||||
|
const SavedFilterDropdownRef = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ style, className }: HTMLAttributes<HTMLDivElement>, ref) => (
|
||||||
|
<div ref={ref} style={style} className={className}>
|
||||||
|
<SavedFilterList {...props} />
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
SavedFilterDropdownRef.displayName = "SavedFilterDropdown";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown>
|
||||||
|
<OverlayTrigger
|
||||||
|
placement="top"
|
||||||
|
overlay={
|
||||||
|
<Tooltip id="filter-tooltip">
|
||||||
|
<FormattedMessage id="search_filter.saved_filters" />
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Dropdown.Toggle variant="secondary">
|
||||||
|
<Icon icon={faBookmark} />
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
</OverlayTrigger>
|
||||||
|
<Dropdown.Menu
|
||||||
|
as={SavedFilterDropdownRef}
|
||||||
|
className="saved-filter-list-menu"
|
||||||
|
/>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ input[type="range"].zoom-slider {
|
|||||||
.set-as-default-button {
|
.set-as-default-button {
|
||||||
float: right;
|
float: right;
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.LoadingIndicator {
|
.LoadingIndicator {
|
||||||
|
|||||||
32
ui/v2.5/src/components/List/util.ts
Normal file
32
ui/v2.5/src/components/List/util.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { useContext, useMemo } from "react";
|
||||||
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
|
import { View } from "./views";
|
||||||
|
|
||||||
|
export function useDefaultFilter(mode: GQL.FilterMode, view?: View) {
|
||||||
|
const emptyFilter = useMemo(() => new ListFilterModel(mode), [mode]);
|
||||||
|
const { configuration: config, loading } = useContext(ConfigurationContext);
|
||||||
|
|
||||||
|
const defaultFilter = useMemo(() => {
|
||||||
|
if (view && config?.ui.defaultFilters?.[view]) {
|
||||||
|
const savedFilter = config.ui.defaultFilters[view]!;
|
||||||
|
const newFilter = emptyFilter.clone();
|
||||||
|
|
||||||
|
newFilter.currentPage = 1;
|
||||||
|
try {
|
||||||
|
newFilter.configureFromSavedFilter(savedFilter);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
// #1507 - reset random seed when loaded
|
||||||
|
newFilter.randomSeed = -1;
|
||||||
|
return newFilter;
|
||||||
|
}
|
||||||
|
}, [view, config?.ui.defaultFilters, emptyFilter]);
|
||||||
|
|
||||||
|
const retFilter = loading ? undefined : defaultFilter ?? emptyFilter;
|
||||||
|
|
||||||
|
return { defaultFilter: retFilter, loading };
|
||||||
|
}
|
||||||
34
ui/v2.5/src/components/List/views.ts
Normal file
34
ui/v2.5/src/components/List/views.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export enum View {
|
||||||
|
Galleries = "galleries",
|
||||||
|
Images = "images",
|
||||||
|
Scenes = "scenes",
|
||||||
|
Movies = "movies",
|
||||||
|
Performers = "performers",
|
||||||
|
Tags = "tags",
|
||||||
|
SceneMarkers = "scene_markers",
|
||||||
|
Studios = "studios",
|
||||||
|
|
||||||
|
TagMarkers = "tag_markers",
|
||||||
|
TagGalleries = "tag_galleries",
|
||||||
|
TagScenes = "tag_scenes",
|
||||||
|
TagImages = "tag_images",
|
||||||
|
TagPerformers = "tag_performers",
|
||||||
|
|
||||||
|
PerformerScenes = "performer_scenes",
|
||||||
|
PerformerGalleries = "performer_galleries",
|
||||||
|
PerformerImages = "performer_images",
|
||||||
|
PerformerMovies = "performer_movies",
|
||||||
|
PerformerAppearsWith = "performer_appears_with",
|
||||||
|
|
||||||
|
StudioGalleries = "studio_galleries",
|
||||||
|
StudioImages = "studio_images",
|
||||||
|
|
||||||
|
GalleryImages = "gallery_images",
|
||||||
|
|
||||||
|
StudioScenes = "studio_scenes",
|
||||||
|
StudioMovies = "studio_movies",
|
||||||
|
StudioPerformers = "studio_performers",
|
||||||
|
StudioChildren = "studio_children",
|
||||||
|
|
||||||
|
MovieScenes = "movie_scenes",
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { MoviesCriterion } from "src/models/list-filter/criteria/movies";
|
import { MoviesCriterion } from "src/models/list-filter/criteria/movies";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { SceneList } from "src/components/Scenes/SceneList";
|
import { SceneList } from "src/components/Scenes/SceneList";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IMovieScenesPanel {
|
interface IMovieScenesPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -51,6 +52,7 @@ export const MovieScenesPanel: React.FC<IMovieScenesPanel> = ({
|
|||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
defaultSort="movie_scene_number"
|
defaultSort="movie_scene_number"
|
||||||
alterQuery={active}
|
alterQuery={active}
|
||||||
|
view={View.MovieScenes}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,12 @@ import {
|
|||||||
useFindMovies,
|
useFindMovies,
|
||||||
useMoviesDestroy,
|
useMoviesDestroy,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import {
|
import { makeItemList, showWhenSelected } from "../List/ItemList";
|
||||||
makeItemList,
|
|
||||||
PersistanceLevel,
|
|
||||||
showWhenSelected,
|
|
||||||
} from "../List/ItemList";
|
|
||||||
import { ExportDialog } from "../Shared/ExportDialog";
|
import { ExportDialog } from "../Shared/ExportDialog";
|
||||||
import { DeleteEntityDialog } from "../Shared/DeleteEntityDialog";
|
import { DeleteEntityDialog } from "../Shared/DeleteEntityDialog";
|
||||||
import { MovieCardGrid } from "./MovieCardGrid";
|
import { MovieCardGrid } from "./MovieCardGrid";
|
||||||
import { EditMoviesDialog } from "./EditMoviesDialog";
|
import { EditMoviesDialog } from "./EditMoviesDialog";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const MovieItemList = makeItemList({
|
const MovieItemList = makeItemList({
|
||||||
filterMode: GQL.FilterMode.Movies,
|
filterMode: GQL.FilterMode.Movies,
|
||||||
@@ -34,10 +31,15 @@ const MovieItemList = makeItemList({
|
|||||||
|
|
||||||
interface IMovieList {
|
interface IMovieList {
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||||
|
view?: View;
|
||||||
alterQuery?: boolean;
|
alterQuery?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MovieList: React.FC<IMovieList> = ({ filterHook, alterQuery }) => {
|
export const MovieList: React.FC<IMovieList> = ({
|
||||||
|
filterHook,
|
||||||
|
alterQuery,
|
||||||
|
view,
|
||||||
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||||
@@ -175,7 +177,7 @@ export const MovieList: React.FC<IMovieList> = ({ filterHook, alterQuery }) => {
|
|||||||
<MovieItemList
|
<MovieItemList
|
||||||
selectable
|
selectable
|
||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
persistState={PersistanceLevel.ALL}
|
view={view}
|
||||||
alterQuery={alterQuery}
|
alterQuery={alterQuery}
|
||||||
otherOperations={otherOperations}
|
otherOperations={otherOperations}
|
||||||
addKeybinds={addKeybinds}
|
addKeybinds={addKeybinds}
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import Movie from "./MovieDetails/Movie";
|
|||||||
import MovieCreate from "./MovieDetails/MovieCreate";
|
import MovieCreate from "./MovieDetails/MovieCreate";
|
||||||
import { MovieList } from "./MovieList";
|
import { MovieList } from "./MovieList";
|
||||||
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const Movies: React.FC = () => {
|
const Movies: React.FC = () => {
|
||||||
useScrollToTopOnMount();
|
useScrollToTopOnMount();
|
||||||
|
|
||||||
return <MovieList />;
|
return <MovieList view={View.Movies} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MovieRoutes: React.FC = () => {
|
const MovieRoutes: React.FC = () => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { GalleryList } from "src/components/Galleries/GalleryList";
|
import { GalleryList } from "src/components/Galleries/GalleryList";
|
||||||
import { usePerformerFilterHook } from "src/core/performers";
|
import { usePerformerFilterHook } from "src/core/performers";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IPerformerDetailsProps {
|
interface IPerformerDetailsProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -13,5 +14,11 @@ export const PerformerGalleriesPanel: React.FC<IPerformerDetailsProps> = ({
|
|||||||
performer,
|
performer,
|
||||||
}) => {
|
}) => {
|
||||||
const filterHook = usePerformerFilterHook(performer);
|
const filterHook = usePerformerFilterHook(performer);
|
||||||
return <GalleryList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<GalleryList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.PerformerGalleries}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { ImageList } from "src/components/Images/ImageList";
|
import { ImageList } from "src/components/Images/ImageList";
|
||||||
import { usePerformerFilterHook } from "src/core/performers";
|
import { usePerformerFilterHook } from "src/core/performers";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IPerformerImagesPanel {
|
interface IPerformerImagesPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -13,5 +14,11 @@ export const PerformerImagesPanel: React.FC<IPerformerImagesPanel> = ({
|
|||||||
performer,
|
performer,
|
||||||
}) => {
|
}) => {
|
||||||
const filterHook = usePerformerFilterHook(performer);
|
const filterHook = usePerformerFilterHook(performer);
|
||||||
return <ImageList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<ImageList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.PerformerImages}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { MovieList } from "src/components/Movies/MovieList";
|
import { MovieList } from "src/components/Movies/MovieList";
|
||||||
import { usePerformerFilterHook } from "src/core/performers";
|
import { usePerformerFilterHook } from "src/core/performers";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IPerformerDetailsProps {
|
interface IPerformerDetailsProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -13,5 +14,11 @@ export const PerformerMoviesPanel: React.FC<IPerformerDetailsProps> = ({
|
|||||||
performer,
|
performer,
|
||||||
}) => {
|
}) => {
|
||||||
const filterHook = usePerformerFilterHook(performer);
|
const filterHook = usePerformerFilterHook(performer);
|
||||||
return <MovieList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<MovieList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.PerformerMovies}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { SceneList } from "src/components/Scenes/SceneList";
|
import { SceneList } from "src/components/Scenes/SceneList";
|
||||||
import { usePerformerFilterHook } from "src/core/performers";
|
import { usePerformerFilterHook } from "src/core/performers";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IPerformerDetailsProps {
|
interface IPerformerDetailsProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -13,5 +14,11 @@ export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> = ({
|
|||||||
performer,
|
performer,
|
||||||
}) => {
|
}) => {
|
||||||
const filterHook = usePerformerFilterHook(performer);
|
const filterHook = usePerformerFilterHook(performer);
|
||||||
return <SceneList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<SceneList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.PerformerScenes}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { PerformerList } from "src/components/Performers/PerformerList";
|
import { PerformerList } from "src/components/Performers/PerformerList";
|
||||||
import { usePerformerFilterHook } from "src/core/performers";
|
import { usePerformerFilterHook } from "src/core/performers";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IPerformerDetailsProps {
|
interface IPerformerDetailsProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -28,6 +29,7 @@ export const PerformerAppearsWithPanel: React.FC<IPerformerDetailsProps> = ({
|
|||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
extraCriteria={extraCriteria}
|
extraCriteria={extraCriteria}
|
||||||
alterQuery={active}
|
alterQuery={active}
|
||||||
|
view={View.PerformerAppearsWith}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ import {
|
|||||||
useFindPerformers,
|
useFindPerformers,
|
||||||
usePerformersDestroy,
|
usePerformersDestroy,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import {
|
import { makeItemList, showWhenSelected } from "../List/ItemList";
|
||||||
makeItemList,
|
|
||||||
PersistanceLevel,
|
|
||||||
showWhenSelected,
|
|
||||||
} from "../List/ItemList";
|
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { DisplayMode } from "src/models/list-filter/types";
|
import { DisplayMode } from "src/models/list-filter/types";
|
||||||
import { PerformerTagger } from "../Tagger/performers/PerformerTagger";
|
import { PerformerTagger } from "../Tagger/performers/PerformerTagger";
|
||||||
@@ -25,6 +21,7 @@ import { EditPerformersDialog } from "./EditPerformersDialog";
|
|||||||
import { cmToImperial, cmToInches, kgToLbs } from "src/utils/units";
|
import { cmToImperial, cmToInches, kgToLbs } from "src/utils/units";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { PerformerCardGrid } from "./PerformerCardGrid";
|
import { PerformerCardGrid } from "./PerformerCardGrid";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const PerformerItemList = makeItemList({
|
const PerformerItemList = makeItemList({
|
||||||
filterMode: GQL.FilterMode.Performers,
|
filterMode: GQL.FilterMode.Performers,
|
||||||
@@ -162,14 +159,14 @@ export const FormatPenisLength = (penis_length?: number | null) => {
|
|||||||
|
|
||||||
interface IPerformerList {
|
interface IPerformerList {
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||||
persistState?: PersistanceLevel;
|
view?: View;
|
||||||
alterQuery?: boolean;
|
alterQuery?: boolean;
|
||||||
extraCriteria?: IPerformerCardExtraCriteria;
|
extraCriteria?: IPerformerCardExtraCriteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PerformerList: React.FC<IPerformerList> = ({
|
export const PerformerList: React.FC<IPerformerList> = ({
|
||||||
filterHook,
|
filterHook,
|
||||||
persistState,
|
view,
|
||||||
alterQuery,
|
alterQuery,
|
||||||
extraCriteria,
|
extraCriteria,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -325,7 +322,7 @@ export const PerformerList: React.FC<IPerformerList> = ({
|
|||||||
<PerformerItemList
|
<PerformerItemList
|
||||||
selectable
|
selectable
|
||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
persistState={persistState}
|
view={view}
|
||||||
alterQuery={alterQuery}
|
alterQuery={alterQuery}
|
||||||
otherOperations={otherOperations}
|
otherOperations={otherOperations}
|
||||||
addKeybinds={addKeybinds}
|
addKeybinds={addKeybinds}
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ import React from "react";
|
|||||||
import { Route, Switch } from "react-router-dom";
|
import { Route, Switch } from "react-router-dom";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTitleProps } from "src/hooks/title";
|
import { useTitleProps } from "src/hooks/title";
|
||||||
import { PersistanceLevel } from "../List/ItemList";
|
|
||||||
import Performer from "./PerformerDetails/Performer";
|
import Performer from "./PerformerDetails/Performer";
|
||||||
import PerformerCreate from "./PerformerDetails/PerformerCreate";
|
import PerformerCreate from "./PerformerDetails/PerformerCreate";
|
||||||
import { PerformerList } from "./PerformerList";
|
import { PerformerList } from "./PerformerList";
|
||||||
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const Performers: React.FC = () => {
|
const Performers: React.FC = () => {
|
||||||
useScrollToTopOnMount();
|
useScrollToTopOnMount();
|
||||||
|
|
||||||
return <PerformerList persistState={PersistanceLevel.ALL} />;
|
return <PerformerList view={View.Performers} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PerformerRoutes: React.FC = () => {
|
const PerformerRoutes: React.FC = () => {
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import { useHistory } from "react-router-dom";
|
|||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { queryFindScenes, useFindScenes } from "src/core/StashService";
|
import { queryFindScenes, useFindScenes } from "src/core/StashService";
|
||||||
import {
|
import { makeItemList, showWhenSelected } from "../List/ItemList";
|
||||||
makeItemList,
|
|
||||||
PersistanceLevel,
|
|
||||||
showWhenSelected,
|
|
||||||
} from "../List/ItemList";
|
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { DisplayMode } from "src/models/list-filter/types";
|
import { DisplayMode } from "src/models/list-filter/types";
|
||||||
import { Tagger } from "../Tagger/scenes/SceneTagger";
|
import { Tagger } from "../Tagger/scenes/SceneTagger";
|
||||||
@@ -28,6 +24,7 @@ import { faPlay } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { SceneMergeModal } from "./SceneMergeDialog";
|
import { SceneMergeModal } from "./SceneMergeDialog";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const SceneItemList = makeItemList({
|
const SceneItemList = makeItemList({
|
||||||
filterMode: GQL.FilterMode.Scenes,
|
filterMode: GQL.FilterMode.Scenes,
|
||||||
@@ -78,14 +75,14 @@ const SceneItemList = makeItemList({
|
|||||||
interface ISceneList {
|
interface ISceneList {
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||||
defaultSort?: string;
|
defaultSort?: string;
|
||||||
persistState?: PersistanceLevel;
|
view?: View;
|
||||||
alterQuery?: boolean;
|
alterQuery?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SceneList: React.FC<ISceneList> = ({
|
export const SceneList: React.FC<ISceneList> = ({
|
||||||
filterHook,
|
filterHook,
|
||||||
defaultSort,
|
defaultSort,
|
||||||
persistState,
|
view,
|
||||||
alterQuery,
|
alterQuery,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -357,7 +354,7 @@ export const SceneList: React.FC<ISceneList> = ({
|
|||||||
zoomable
|
zoomable
|
||||||
selectable
|
selectable
|
||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
persistState={persistState}
|
view={view}
|
||||||
alterQuery={alterQuery}
|
alterQuery={alterQuery}
|
||||||
otherOperations={otherOperations}
|
otherOperations={otherOperations}
|
||||||
addKeybinds={addKeybinds}
|
addKeybinds={addKeybinds}
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ import {
|
|||||||
useFindSceneMarkers,
|
useFindSceneMarkers,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import NavUtils from "src/utils/navigation";
|
import NavUtils from "src/utils/navigation";
|
||||||
import { makeItemList, PersistanceLevel } from "../List/ItemList";
|
import { makeItemList } from "../List/ItemList";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { DisplayMode } from "src/models/list-filter/types";
|
import { DisplayMode } from "src/models/list-filter/types";
|
||||||
import { MarkerWallPanel } from "../Wall/WallPanel";
|
import { MarkerWallPanel } from "../Wall/WallPanel";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const SceneMarkerItemList = makeItemList({
|
const SceneMarkerItemList = makeItemList({
|
||||||
filterMode: GQL.FilterMode.SceneMarkers,
|
filterMode: GQL.FilterMode.SceneMarkers,
|
||||||
@@ -27,11 +28,13 @@ const SceneMarkerItemList = makeItemList({
|
|||||||
|
|
||||||
interface ISceneMarkerList {
|
interface ISceneMarkerList {
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||||
|
view?: View;
|
||||||
alterQuery?: boolean;
|
alterQuery?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SceneMarkerList: React.FC<ISceneMarkerList> = ({
|
export const SceneMarkerList: React.FC<ISceneMarkerList> = ({
|
||||||
filterHook,
|
filterHook,
|
||||||
|
view,
|
||||||
alterQuery,
|
alterQuery,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -96,7 +99,7 @@ export const SceneMarkerList: React.FC<ISceneMarkerList> = ({
|
|||||||
return (
|
return (
|
||||||
<SceneMarkerItemList
|
<SceneMarkerItemList
|
||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
persistState={PersistanceLevel.ALL}
|
view={view}
|
||||||
alterQuery={alterQuery}
|
alterQuery={alterQuery}
|
||||||
otherOperations={otherOperations}
|
otherOperations={otherOperations}
|
||||||
addKeybinds={addKeybinds}
|
addKeybinds={addKeybinds}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import React from "react";
|
|||||||
import { Route, Switch } from "react-router-dom";
|
import { Route, Switch } from "react-router-dom";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTitleProps } from "src/hooks/title";
|
import { useTitleProps } from "src/hooks/title";
|
||||||
import { PersistanceLevel } from "../List/ItemList";
|
|
||||||
import { lazyComponent } from "src/utils/lazyComponent";
|
import { lazyComponent } from "src/utils/lazyComponent";
|
||||||
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const SceneList = lazyComponent(() => import("./SceneList"));
|
const SceneList = lazyComponent(() => import("./SceneList"));
|
||||||
const SceneMarkerList = lazyComponent(() => import("./SceneMarkerList"));
|
const SceneMarkerList = lazyComponent(() => import("./SceneMarkerList"));
|
||||||
@@ -14,7 +14,7 @@ const SceneCreate = lazyComponent(() => import("./SceneDetails/SceneCreate"));
|
|||||||
const Scenes: React.FC = () => {
|
const Scenes: React.FC = () => {
|
||||||
useScrollToTopOnMount();
|
useScrollToTopOnMount();
|
||||||
|
|
||||||
return <SceneList persistState={PersistanceLevel.ALL} />;
|
return <SceneList view={View.Scenes} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SceneMarkers: React.FC = () => {
|
const SceneMarkers: React.FC = () => {
|
||||||
@@ -24,7 +24,7 @@ const SceneMarkers: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet {...titleProps} />
|
<Helmet {...titleProps} />
|
||||||
<SceneMarkerList />
|
<SceneMarkerList view={View.SceneMarkers} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { ParentStudiosCriterion } from "src/models/list-filter/criteria/studios";
|
import { ParentStudiosCriterion } from "src/models/list-filter/criteria/studios";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { StudioList } from "../StudioList";
|
import { StudioList } from "../StudioList";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IStudioChildrenPanel {
|
interface IStudioChildrenPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -45,5 +46,12 @@ export const StudioChildrenPanel: React.FC<IStudioChildrenPanel> = ({
|
|||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <StudioList fromParent filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<StudioList
|
||||||
|
fromParent
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.StudioChildren}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { GalleryList } from "src/components/Galleries/GalleryList";
|
import { GalleryList } from "src/components/Galleries/GalleryList";
|
||||||
import { useStudioFilterHook } from "src/core/studios";
|
import { useStudioFilterHook } from "src/core/studios";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IStudioGalleriesPanel {
|
interface IStudioGalleriesPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -13,5 +14,11 @@ export const StudioGalleriesPanel: React.FC<IStudioGalleriesPanel> = ({
|
|||||||
studio,
|
studio,
|
||||||
}) => {
|
}) => {
|
||||||
const filterHook = useStudioFilterHook(studio);
|
const filterHook = useStudioFilterHook(studio);
|
||||||
return <GalleryList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<GalleryList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.StudioGalleries}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useStudioFilterHook } from "src/core/studios";
|
import { useStudioFilterHook } from "src/core/studios";
|
||||||
import { ImageList } from "src/components/Images/ImageList";
|
import { ImageList } from "src/components/Images/ImageList";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IStudioImagesPanel {
|
interface IStudioImagesPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -13,5 +14,11 @@ export const StudioImagesPanel: React.FC<IStudioImagesPanel> = ({
|
|||||||
studio,
|
studio,
|
||||||
}) => {
|
}) => {
|
||||||
const filterHook = useStudioFilterHook(studio);
|
const filterHook = useStudioFilterHook(studio);
|
||||||
return <ImageList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<ImageList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.StudioImages}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { MovieList } from "src/components/Movies/MovieList";
|
import { MovieList } from "src/components/Movies/MovieList";
|
||||||
import { useStudioFilterHook } from "src/core/studios";
|
import { useStudioFilterHook } from "src/core/studios";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IStudioMoviesPanel {
|
interface IStudioMoviesPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -13,5 +14,11 @@ export const StudioMoviesPanel: React.FC<IStudioMoviesPanel> = ({
|
|||||||
studio,
|
studio,
|
||||||
}) => {
|
}) => {
|
||||||
const filterHook = useStudioFilterHook(studio);
|
const filterHook = useStudioFilterHook(studio);
|
||||||
return <MovieList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<MovieList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.StudioMovies}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { useStudioFilterHook } from "src/core/studios";
|
import { useStudioFilterHook } from "src/core/studios";
|
||||||
import { PerformerList } from "src/components/Performers/PerformerList";
|
import { PerformerList } from "src/components/Performers/PerformerList";
|
||||||
import { StudiosCriterion } from "src/models/list-filter/criteria/studios";
|
import { StudiosCriterion } from "src/models/list-filter/criteria/studios";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IStudioPerformersPanel {
|
interface IStudioPerformersPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -34,6 +35,7 @@ export const StudioPerformersPanel: React.FC<IStudioPerformersPanel> = ({
|
|||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
extraCriteria={extraCriteria}
|
extraCriteria={extraCriteria}
|
||||||
alterQuery={active}
|
alterQuery={active}
|
||||||
|
view={View.StudioPerformers}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { SceneList } from "src/components/Scenes/SceneList";
|
import { SceneList } from "src/components/Scenes/SceneList";
|
||||||
import { useStudioFilterHook } from "src/core/studios";
|
import { useStudioFilterHook } from "src/core/studios";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface IStudioScenesPanel {
|
interface IStudioScenesPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -13,5 +14,11 @@ export const StudioScenesPanel: React.FC<IStudioScenesPanel> = ({
|
|||||||
studio,
|
studio,
|
||||||
}) => {
|
}) => {
|
||||||
const filterHook = useStudioFilterHook(studio);
|
const filterHook = useStudioFilterHook(studio);
|
||||||
return <SceneList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<SceneList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.StudioScenes}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,17 +9,14 @@ import {
|
|||||||
useFindStudios,
|
useFindStudios,
|
||||||
useStudiosDestroy,
|
useStudiosDestroy,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import {
|
import { makeItemList, showWhenSelected } from "../List/ItemList";
|
||||||
makeItemList,
|
|
||||||
PersistanceLevel,
|
|
||||||
showWhenSelected,
|
|
||||||
} from "../List/ItemList";
|
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { DisplayMode } from "src/models/list-filter/types";
|
import { DisplayMode } from "src/models/list-filter/types";
|
||||||
import { ExportDialog } from "../Shared/ExportDialog";
|
import { ExportDialog } from "../Shared/ExportDialog";
|
||||||
import { DeleteEntityDialog } from "../Shared/DeleteEntityDialog";
|
import { DeleteEntityDialog } from "../Shared/DeleteEntityDialog";
|
||||||
import { StudioTagger } from "../Tagger/studios/StudioTagger";
|
import { StudioTagger } from "../Tagger/studios/StudioTagger";
|
||||||
import { StudioCardGrid } from "./StudioCardGrid";
|
import { StudioCardGrid } from "./StudioCardGrid";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const StudioItemList = makeItemList({
|
const StudioItemList = makeItemList({
|
||||||
filterMode: GQL.FilterMode.Studios,
|
filterMode: GQL.FilterMode.Studios,
|
||||||
@@ -35,12 +32,14 @@ const StudioItemList = makeItemList({
|
|||||||
interface IStudioList {
|
interface IStudioList {
|
||||||
fromParent?: boolean;
|
fromParent?: boolean;
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||||
|
view?: View;
|
||||||
alterQuery?: boolean;
|
alterQuery?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StudioList: React.FC<IStudioList> = ({
|
export const StudioList: React.FC<IStudioList> = ({
|
||||||
fromParent,
|
fromParent,
|
||||||
filterHook,
|
filterHook,
|
||||||
|
view,
|
||||||
alterQuery,
|
alterQuery,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -181,7 +180,7 @@ export const StudioList: React.FC<IStudioList> = ({
|
|||||||
<StudioItemList
|
<StudioItemList
|
||||||
selectable
|
selectable
|
||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
persistState={fromParent ? PersistanceLevel.NONE : PersistanceLevel.ALL}
|
view={view}
|
||||||
alterQuery={alterQuery}
|
alterQuery={alterQuery}
|
||||||
otherOperations={otherOperations}
|
otherOperations={otherOperations}
|
||||||
addKeybinds={addKeybinds}
|
addKeybinds={addKeybinds}
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import Studio from "./StudioDetails/Studio";
|
|||||||
import StudioCreate from "./StudioDetails/StudioCreate";
|
import StudioCreate from "./StudioDetails/StudioCreate";
|
||||||
import { StudioList } from "./StudioList";
|
import { StudioList } from "./StudioList";
|
||||||
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
const Studios: React.FC = () => {
|
const Studios: React.FC = () => {
|
||||||
useScrollToTopOnMount();
|
useScrollToTopOnMount();
|
||||||
|
|
||||||
return <StudioList />;
|
return <StudioList view={View.Studios} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StudioRoutes: React.FC = () => {
|
const StudioRoutes: React.FC = () => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useTagFilterHook } from "src/core/tags";
|
import { useTagFilterHook } from "src/core/tags";
|
||||||
import { GalleryList } from "src/components/Galleries/GalleryList";
|
import { GalleryList } from "src/components/Galleries/GalleryList";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface ITagGalleriesPanel {
|
interface ITagGalleriesPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -13,5 +14,11 @@ export const TagGalleriesPanel: React.FC<ITagGalleriesPanel> = ({
|
|||||||
tag,
|
tag,
|
||||||
}) => {
|
}) => {
|
||||||
const filterHook = useTagFilterHook(tag);
|
const filterHook = useTagFilterHook(tag);
|
||||||
return <GalleryList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<GalleryList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.TagGalleries}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useTagFilterHook } from "src/core/tags";
|
import { useTagFilterHook } from "src/core/tags";
|
||||||
import { ImageList } from "src/components/Images/ImageList";
|
import { ImageList } from "src/components/Images/ImageList";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface ITagImagesPanel {
|
interface ITagImagesPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -10,5 +11,11 @@ interface ITagImagesPanel {
|
|||||||
|
|
||||||
export const TagImagesPanel: React.FC<ITagImagesPanel> = ({ active, tag }) => {
|
export const TagImagesPanel: React.FC<ITagImagesPanel> = ({ active, tag }) => {
|
||||||
const filterHook = useTagFilterHook(tag);
|
const filterHook = useTagFilterHook(tag);
|
||||||
return <ImageList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<ImageList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.TagImages}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
TagsCriterionOption,
|
TagsCriterionOption,
|
||||||
} from "src/models/list-filter/criteria/tags";
|
} from "src/models/list-filter/criteria/tags";
|
||||||
import { SceneMarkerList } from "src/components/Scenes/SceneMarkerList";
|
import { SceneMarkerList } from "src/components/Scenes/SceneMarkerList";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface ITagMarkersPanel {
|
interface ITagMarkersPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -52,5 +53,11 @@ export const TagMarkersPanel: React.FC<ITagMarkersPanel> = ({
|
|||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SceneMarkerList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<SceneMarkerList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.TagMarkers}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useTagFilterHook } from "src/core/tags";
|
import { useTagFilterHook } from "src/core/tags";
|
||||||
import { PerformerList } from "src/components/Performers/PerformerList";
|
import { PerformerList } from "src/components/Performers/PerformerList";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface ITagPerformersPanel {
|
interface ITagPerformersPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -13,5 +14,11 @@ export const TagPerformersPanel: React.FC<ITagPerformersPanel> = ({
|
|||||||
tag,
|
tag,
|
||||||
}) => {
|
}) => {
|
||||||
const filterHook = useTagFilterHook(tag);
|
const filterHook = useTagFilterHook(tag);
|
||||||
return <PerformerList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<PerformerList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.TagPerformers}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { SceneList } from "src/components/Scenes/SceneList";
|
import { SceneList } from "src/components/Scenes/SceneList";
|
||||||
import { useTagFilterHook } from "src/core/tags";
|
import { useTagFilterHook } from "src/core/tags";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
interface ITagScenesPanel {
|
interface ITagScenesPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -10,5 +11,11 @@ interface ITagScenesPanel {
|
|||||||
|
|
||||||
export const TagScenesPanel: React.FC<ITagScenesPanel> = ({ active, tag }) => {
|
export const TagScenesPanel: React.FC<ITagScenesPanel> = ({ active, tag }) => {
|
||||||
const filterHook = useTagFilterHook(tag);
|
const filterHook = useTagFilterHook(tag);
|
||||||
return <SceneList filterHook={filterHook} alterQuery={active} />;
|
return (
|
||||||
|
<SceneList
|
||||||
|
filterHook={filterHook}
|
||||||
|
alterQuery={active}
|
||||||
|
view={View.TagScenes}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ import cloneDeep from "lodash-es/cloneDeep";
|
|||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { DisplayMode } from "src/models/list-filter/types";
|
import { DisplayMode } from "src/models/list-filter/types";
|
||||||
import {
|
import { makeItemList, showWhenSelected } from "../List/ItemList";
|
||||||
makeItemList,
|
|
||||||
PersistanceLevel,
|
|
||||||
showWhenSelected,
|
|
||||||
} from "../List/ItemList";
|
|
||||||
import { Button } from "react-bootstrap";
|
import { Button } from "react-bootstrap";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
import { Link, useHistory } from "react-router-dom";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
@@ -29,6 +25,7 @@ import { tagRelationHook } from "../../core/tags";
|
|||||||
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { TagCardGrid } from "./TagCardGrid";
|
import { TagCardGrid } from "./TagCardGrid";
|
||||||
import { EditTagsDialog } from "./EditTagsDialog";
|
import { EditTagsDialog } from "./EditTagsDialog";
|
||||||
|
import { View } from "../List/views";
|
||||||
|
|
||||||
interface ITagList {
|
interface ITagList {
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||||
@@ -363,7 +360,7 @@ export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
|
|||||||
zoomable
|
zoomable
|
||||||
defaultZoomIndex={0}
|
defaultZoomIndex={0}
|
||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
persistState={PersistanceLevel.ALL}
|
view={View.Tags}
|
||||||
alterQuery={alterQuery}
|
alterQuery={alterQuery}
|
||||||
otherOperations={otherOperations}
|
otherOperations={otherOperations}
|
||||||
addKeybinds={addKeybinds}
|
addKeybinds={addKeybinds}
|
||||||
|
|||||||
@@ -452,11 +452,6 @@ export const useFindSavedFilters = (mode?: GQL.FilterMode) =>
|
|||||||
variables: { mode },
|
variables: { mode },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useFindDefaultFilter = (mode: GQL.FilterMode) =>
|
|
||||||
GQL.useFindDefaultFilterQuery({
|
|
||||||
variables: { mode },
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Object Mutations
|
/// Object Mutations
|
||||||
|
|
||||||
// Increases/decreases the given field of the Stats query by diff
|
// Increases/decreases the given field of the Stats query by diff
|
||||||
@@ -1956,15 +1951,6 @@ export const useSaveFilter = () =>
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useSetDefaultFilter = () =>
|
|
||||||
GQL.useSetDefaultFilterMutation({
|
|
||||||
update(cache, result) {
|
|
||||||
if (!result.data?.setDefaultFilter) return;
|
|
||||||
|
|
||||||
evictQueries(cache, [GQL.FindDefaultFilterDocument]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useSavedFilterDestroy = () =>
|
export const useSavedFilterDestroy = () =>
|
||||||
GQL.useDestroySavedFilterMutation({
|
GQL.useDestroySavedFilterMutation({
|
||||||
update(cache, result, { variables }) {
|
update(cache, result, { variables }) {
|
||||||
@@ -1972,8 +1958,6 @@ export const useSavedFilterDestroy = () =>
|
|||||||
|
|
||||||
const obj = { __typename: "SavedFilter", id: variables.input.id };
|
const obj = { __typename: "SavedFilter", id: variables.input.id };
|
||||||
deleteObject(cache, obj, GQL.FindSavedFilterDocument);
|
deleteObject(cache, obj, GQL.FindSavedFilterDocument);
|
||||||
|
|
||||||
evictQueries(cache, [GQL.FindDefaultFilterDocument]);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import { IntlShape } from "react-intl";
|
|||||||
import { ITypename } from "src/utils/data";
|
import { ITypename } from "src/utils/data";
|
||||||
import { ImageWallOptions } from "src/utils/imageWall";
|
import { ImageWallOptions } from "src/utils/imageWall";
|
||||||
import { RatingSystemOptions } from "src/utils/rating";
|
import { RatingSystemOptions } from "src/utils/rating";
|
||||||
import { FilterMode, SortDirectionEnum } from "./generated-graphql";
|
import {
|
||||||
|
FilterMode,
|
||||||
|
SavedFilterDataFragment,
|
||||||
|
SortDirectionEnum,
|
||||||
|
} from "./generated-graphql";
|
||||||
|
import { View } from "src/components/List/views";
|
||||||
|
|
||||||
// NOTE: double capitals aren't converted correctly in the backend
|
// NOTE: double capitals aren't converted correctly in the backend
|
||||||
|
|
||||||
@@ -25,6 +30,10 @@ export interface ICustomFilter extends ITypename {
|
|||||||
direction: SortDirectionEnum;
|
direction: SortDirectionEnum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DefaultFilters = {
|
||||||
|
[P in View]?: SavedFilterDataFragment;
|
||||||
|
};
|
||||||
|
|
||||||
export type FrontPageContent = ISavedFilterRow | ICustomFilter;
|
export type FrontPageContent = ISavedFilterRow | ICustomFilter;
|
||||||
|
|
||||||
export const defaultMaxOptionsShown = 200;
|
export const defaultMaxOptionsShown = 200;
|
||||||
@@ -86,6 +95,8 @@ export interface IUIConfig {
|
|||||||
advancedMode?: boolean;
|
advancedMode?: boolean;
|
||||||
|
|
||||||
taskDefaults?: Record<string, {}>;
|
taskDefaults?: Record<string, {}>;
|
||||||
|
|
||||||
|
defaultFilters?: DefaultFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFrontPageContent(
|
export function getFrontPageContent(
|
||||||
|
|||||||
@@ -61,9 +61,6 @@ const typePolicies: TypePolicies = {
|
|||||||
findSavedFilter: {
|
findSavedFilter: {
|
||||||
read: readReference("SavedFilter"),
|
read: readReference("SavedFilter"),
|
||||||
},
|
},
|
||||||
findDefaultFilter: {
|
|
||||||
read: readDanglingNull,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Scene: {
|
Scene: {
|
||||||
|
|||||||
1
ui/v2.5/src/docs/en/MigrationNotes/60.md
Normal file
1
ui/v2.5/src/docs/en/MigrationNotes/60.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This migration moves default filters from the database into the configuration file. A backup of the current `config.yml` will be created in the same directory with the name `config.yml.59.<date and time>`. The exact filename is written to the log.
|
||||||
@@ -2,10 +2,12 @@ import migration32 from "./32.md";
|
|||||||
import migration39 from "./39.md";
|
import migration39 from "./39.md";
|
||||||
import migration48 from "./48.md";
|
import migration48 from "./48.md";
|
||||||
import migration58 from "./58.md";
|
import migration58 from "./58.md";
|
||||||
|
import migration60 from "./60.md";
|
||||||
|
|
||||||
export const migrationNotes: Record<number, string> = {
|
export const migrationNotes: Record<number, string> = {
|
||||||
32: migration32,
|
32: migration32,
|
||||||
39: migration39,
|
39: migration39,
|
||||||
48: migration48,
|
48: migration48,
|
||||||
58: migration58,
|
58: migration58,
|
||||||
|
60: migration60,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
SavedObjectFilter,
|
SavedObjectFilter,
|
||||||
SavedUIOptions,
|
SavedUIOptions,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { ListFilterOptions } from "./filter-options";
|
||||||
|
|
||||||
interface IDecodedParams {
|
interface IDecodedParams {
|
||||||
perPage?: number;
|
perPage?: number;
|
||||||
@@ -49,7 +50,8 @@ const DEFAULT_PARAMS = {
|
|||||||
|
|
||||||
// TODO: handle customCriteria
|
// TODO: handle customCriteria
|
||||||
export class ListFilterModel {
|
export class ListFilterModel {
|
||||||
public mode: FilterMode;
|
public readonly mode: FilterMode;
|
||||||
|
public readonly options: ListFilterOptions;
|
||||||
private config?: ConfigDataFragment;
|
private config?: ConfigDataFragment;
|
||||||
public searchTerm: string = "";
|
public searchTerm: string = "";
|
||||||
public currentPage = DEFAULT_PARAMS.currentPage;
|
public currentPage = DEFAULT_PARAMS.currentPage;
|
||||||
@@ -65,19 +67,18 @@ export class ListFilterModel {
|
|||||||
public constructor(
|
public constructor(
|
||||||
mode: FilterMode,
|
mode: FilterMode,
|
||||||
config?: ConfigDataFragment,
|
config?: ConfigDataFragment,
|
||||||
defaultSort?: string,
|
|
||||||
defaultDisplayMode?: DisplayMode,
|
|
||||||
defaultZoomIndex?: number
|
defaultZoomIndex?: number
|
||||||
) {
|
) {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.sortBy = defaultSort;
|
this.options = getFilterOptions(mode);
|
||||||
|
const { defaultSortBy, displayModeOptions } = this.options;
|
||||||
|
|
||||||
|
this.sortBy = defaultSortBy;
|
||||||
if (this.sortBy === "date") {
|
if (this.sortBy === "date") {
|
||||||
this.sortDirection = SortDirectionEnum.Desc;
|
this.sortDirection = SortDirectionEnum.Desc;
|
||||||
}
|
}
|
||||||
if (defaultDisplayMode !== undefined) {
|
this.displayMode = displayModeOptions[0];
|
||||||
this.displayMode = defaultDisplayMode;
|
|
||||||
}
|
|
||||||
if (defaultZoomIndex !== undefined) {
|
if (defaultZoomIndex !== undefined) {
|
||||||
this.defaultZoomIndex = defaultZoomIndex;
|
this.defaultZoomIndex = defaultZoomIndex;
|
||||||
this.zoomIndex = defaultZoomIndex;
|
this.zoomIndex = defaultZoomIndex;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { FilterMode, Scene } from "src/core/generated-graphql";
|
import { FilterMode, Scene } from "src/core/generated-graphql";
|
||||||
import { ListFilterModel } from "./list-filter/filter";
|
import { ListFilterModel } from "./list-filter/filter";
|
||||||
import { SceneListFilterOptions } from "./list-filter/scenes";
|
|
||||||
import { INamedObject } from "src/utils/navigation";
|
import { INamedObject } from "src/utils/navigation";
|
||||||
|
|
||||||
export type QueuedScene = Pick<Scene, "id" | "title" | "date" | "paths"> & {
|
export type QueuedScene = Pick<Scene, "id" | "title" | "date" | "paths"> & {
|
||||||
@@ -97,11 +96,7 @@ export class SceneQueue {
|
|||||||
c: params.getAll("qfc"),
|
c: params.getAll("qfc"),
|
||||||
};
|
};
|
||||||
const decoded = ListFilterModel.decodeParams(translated);
|
const decoded = ListFilterModel.decodeParams(translated);
|
||||||
const query = new ListFilterModel(
|
const query = new ListFilterModel(FilterMode.Scenes);
|
||||||
FilterMode.Scenes,
|
|
||||||
undefined,
|
|
||||||
SceneListFilterOptions.defaultSortBy
|
|
||||||
);
|
|
||||||
query.configureFromDecodedParams(decoded);
|
query.configureFromDecodedParams(decoded);
|
||||||
ret.query = query;
|
ret.query = query;
|
||||||
} else if (params.has("qs")) {
|
} else if (params.has("qs")) {
|
||||||
|
|||||||
6
ui/v2.5/src/pluginApi.d.ts
vendored
6
ui/v2.5/src/pluginApi.d.ts
vendored
@@ -39,7 +39,6 @@ declare namespace PluginApi {
|
|||||||
const EnableDlnaDocument: { [key: string]: any };
|
const EnableDlnaDocument: { [key: string]: any };
|
||||||
const ExportObjectsDocument: { [key: string]: any };
|
const ExportObjectsDocument: { [key: string]: any };
|
||||||
const FilterMode: { [key: string]: any };
|
const FilterMode: { [key: string]: any };
|
||||||
const FindDefaultFilterDocument: { [key: string]: any };
|
|
||||||
const FindDuplicateScenesDocument: { [key: string]: any };
|
const FindDuplicateScenesDocument: { [key: string]: any };
|
||||||
const FindGalleriesDocument: { [key: string]: any };
|
const FindGalleriesDocument: { [key: string]: any };
|
||||||
const FindGalleriesForSelectDocument: { [key: string]: any };
|
const FindGalleriesForSelectDocument: { [key: string]: any };
|
||||||
@@ -208,7 +207,6 @@ declare namespace PluginApi {
|
|||||||
const SelectPerformerDataFragmentDoc: { [key: string]: any };
|
const SelectPerformerDataFragmentDoc: { [key: string]: any };
|
||||||
const SelectStudioDataFragmentDoc: { [key: string]: any };
|
const SelectStudioDataFragmentDoc: { [key: string]: any };
|
||||||
const SelectTagDataFragmentDoc: { [key: string]: any };
|
const SelectTagDataFragmentDoc: { [key: string]: any };
|
||||||
const SetDefaultFilterDocument: { [key: string]: any };
|
|
||||||
const SetPluginsEnabledDocument: { [key: string]: any };
|
const SetPluginsEnabledDocument: { [key: string]: any };
|
||||||
const SetupDocument: { [key: string]: any };
|
const SetupDocument: { [key: string]: any };
|
||||||
const SlimGalleryDataFragmentDoc: { [key: string]: any };
|
const SlimGalleryDataFragmentDoc: { [key: string]: any };
|
||||||
@@ -254,7 +252,6 @@ declare namespace PluginApi {
|
|||||||
function refetchConfigurationQuery(...args: any[]): any;
|
function refetchConfigurationQuery(...args: any[]): any;
|
||||||
function refetchDirectoryQuery(...args: any[]): any;
|
function refetchDirectoryQuery(...args: any[]): any;
|
||||||
function refetchDlnaStatusQuery(...args: any[]): any;
|
function refetchDlnaStatusQuery(...args: any[]): any;
|
||||||
function refetchFindDefaultFilterQuery(...args: any[]): any;
|
|
||||||
function refetchFindDuplicateScenesQuery(...args: any[]): any;
|
function refetchFindDuplicateScenesQuery(...args: any[]): any;
|
||||||
function refetchFindGalleriesForSelectQuery(...args: any[]): any;
|
function refetchFindGalleriesForSelectQuery(...args: any[]): any;
|
||||||
function refetchFindGalleriesQuery(...args: any[]): any;
|
function refetchFindGalleriesQuery(...args: any[]): any;
|
||||||
@@ -349,9 +346,6 @@ declare namespace PluginApi {
|
|||||||
function useDlnaStatusSuspenseQuery(...args: any[]): any;
|
function useDlnaStatusSuspenseQuery(...args: any[]): any;
|
||||||
function useEnableDlnaMutation(...args: any[]): any;
|
function useEnableDlnaMutation(...args: any[]): any;
|
||||||
function useExportObjectsMutation(...args: any[]): any;
|
function useExportObjectsMutation(...args: any[]): any;
|
||||||
function useFindDefaultFilterLazyQuery(...args: any[]): any;
|
|
||||||
function useFindDefaultFilterQuery(...args: any[]): any;
|
|
||||||
function useFindDefaultFilterSuspenseQuery(...args: any[]): any;
|
|
||||||
function useFindDuplicateScenesLazyQuery(...args: any[]): any;
|
function useFindDuplicateScenesLazyQuery(...args: any[]): any;
|
||||||
function useFindDuplicateScenesQuery(...args: any[]): any;
|
function useFindDuplicateScenesQuery(...args: any[]): any;
|
||||||
function useFindDuplicateScenesSuspenseQuery(...args: any[]): any;
|
function useFindDuplicateScenesSuspenseQuery(...args: any[]): any;
|
||||||
|
|||||||
Reference in New Issue
Block a user