mirror of
https://github.com/stashapp/stash.git
synced 2025-12-15 12:52:38 +01:00
Save task options (#4620)
* Support setting nested UI values * Accept partial for configureUI * Send partial UI * Save scan, generate and auto-tag options on change * Send partials in saveUI * Save library task options on change
This commit is contained in:
parent
bf7cb78d6d
commit
7ac7963972
9 changed files with 383 additions and 54 deletions
|
|
@ -352,9 +352,12 @@ type Mutation {
|
||||||
# overwrites the entire plugin configuration for the given plugin
|
# overwrites the entire plugin configuration for the given plugin
|
||||||
configurePlugin(plugin_id: ID!, input: Map!): Map!
|
configurePlugin(plugin_id: ID!, input: Map!): Map!
|
||||||
|
|
||||||
# overwrites the entire UI configuration
|
# overwrites the UI configuration
|
||||||
configureUI(input: Map!): Map!
|
# if input is provided, then the entire UI configuration is replaced
|
||||||
|
# if partial is provided, then the partial UI configuration is merged into the existing UI configuration
|
||||||
|
configureUI(input: Map, partial: Map): Map!
|
||||||
# sets a single UI key value
|
# sets a single UI key value
|
||||||
|
# key is a dot separated path to the value
|
||||||
configureUISetting(key: String!, value: Any): Map!
|
configureUISetting(key: String!, value: Any): Map!
|
||||||
|
|
||||||
"Generate and set (or clear) API key"
|
"Generate and set (or clear) API key"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/stashapp/stash/pkg/fsutil"
|
"github.com/stashapp/stash/pkg/fsutil"
|
||||||
"github.com/stashapp/stash/pkg/logger"
|
"github.com/stashapp/stash/pkg/logger"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrOverriddenConfig = errors.New("cannot set overridden value")
|
var ErrOverriddenConfig = errors.New("cannot set overridden value")
|
||||||
|
|
@ -639,9 +640,19 @@ func (r *mutationResolver) GenerateAPIKey(ctx context.Context, input GenerateAPI
|
||||||
return newAPIKey, nil
|
return newAPIKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) ConfigureUI(ctx context.Context, input map[string]interface{}) (map[string]interface{}, error) {
|
func (r *mutationResolver) ConfigureUI(ctx context.Context, input map[string]interface{}, partial map[string]interface{}) (map[string]interface{}, error) {
|
||||||
c := config.GetInstance()
|
c := config.GetInstance()
|
||||||
c.SetUIConfiguration(input)
|
|
||||||
|
if input != nil {
|
||||||
|
c.SetUIConfiguration(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if partial != nil {
|
||||||
|
// merge partial into existing config
|
||||||
|
existing := c.GetUIConfiguration()
|
||||||
|
utils.MergeMaps(existing, partial)
|
||||||
|
c.SetUIConfiguration(existing)
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.Write(); err != nil {
|
if err := c.Write(); err != nil {
|
||||||
return c.GetUIConfiguration(), err
|
return c.GetUIConfiguration(), err
|
||||||
|
|
@ -653,10 +664,10 @@ func (r *mutationResolver) ConfigureUI(ctx context.Context, input map[string]int
|
||||||
func (r *mutationResolver) ConfigureUISetting(ctx context.Context, key string, value interface{}) (map[string]interface{}, error) {
|
func (r *mutationResolver) ConfigureUISetting(ctx context.Context, key string, value interface{}) (map[string]interface{}, error) {
|
||||||
c := config.GetInstance()
|
c := config.GetInstance()
|
||||||
|
|
||||||
cfg := c.GetUIConfiguration()
|
cfg := utils.NestedMap(c.GetUIConfiguration())
|
||||||
cfg[key] = value
|
cfg.Set(key, value)
|
||||||
|
|
||||||
return r.ConfigureUI(ctx, cfg)
|
return r.ConfigureUI(ctx, cfg, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) ConfigurePlugin(ctx context.Context, pluginID string, input map[string]interface{}) (map[string]interface{}, error) {
|
func (r *mutationResolver) ConfigurePlugin(ctx context.Context, pluginID string, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
|
|
||||||
64
pkg/utils/map.go
Normal file
64
pkg/utils/map.go
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NestedMap is a map that supports nested keys.
|
||||||
|
// It is expected that the nested maps are of type map[string]interface{}
|
||||||
|
type NestedMap map[string]interface{}
|
||||||
|
|
||||||
|
func (m NestedMap) Get(key string) (interface{}, bool) {
|
||||||
|
fields := strings.Split(key, ".")
|
||||||
|
|
||||||
|
current := m
|
||||||
|
|
||||||
|
for _, f := range fields[:len(fields)-1] {
|
||||||
|
v, found := current[f]
|
||||||
|
if !found {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
current, _ = v.(map[string]interface{})
|
||||||
|
if current == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, found := current[fields[len(fields)-1]]
|
||||||
|
return ret, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m NestedMap) Set(key string, value interface{}) {
|
||||||
|
fields := strings.Split(key, ".")
|
||||||
|
|
||||||
|
current := m
|
||||||
|
|
||||||
|
for _, f := range fields[:len(fields)-1] {
|
||||||
|
v, ok := current[f].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
v = make(map[string]interface{})
|
||||||
|
current[f] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
current = v
|
||||||
|
}
|
||||||
|
|
||||||
|
current[fields[len(fields)-1]] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeMaps merges src into dest. If a key exists in both maps, the value from src is used.
|
||||||
|
func MergeMaps(dest map[string]interface{}, src map[string]interface{}) {
|
||||||
|
for k, v := range src {
|
||||||
|
if _, ok := dest[k]; ok {
|
||||||
|
if srcMap, ok := v.(map[string]interface{}); ok {
|
||||||
|
if destMap, ok := dest[k].(map[string]interface{}); ok {
|
||||||
|
MergeMaps(destMap, srcMap)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dest[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
218
pkg/utils/map_test.go
Normal file
218
pkg/utils/map_test.go
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNestedMapGet(t *testing.T) {
|
||||||
|
m := NestedMap{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"baz": "qux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
key string
|
||||||
|
want interface{}
|
||||||
|
found bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get a value from a nested map",
|
||||||
|
key: "foo.bar.baz",
|
||||||
|
want: "qux",
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get a value from a nested map with a missing key",
|
||||||
|
key: "foo.bar.quux",
|
||||||
|
want: nil,
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get a value from a nested map with a missing key",
|
||||||
|
key: "foo.quux.baz",
|
||||||
|
want: nil,
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get a value from a nested map with a missing key",
|
||||||
|
key: "quux.bar.baz",
|
||||||
|
want: nil,
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get a value from a nested map with a missing key",
|
||||||
|
key: "foo.bar",
|
||||||
|
want: map[string]interface{}{"baz": "qux"},
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, found := m.Get(tt.key)
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("NestedMap.Get() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
if found != tt.found {
|
||||||
|
t.Errorf("NestedMap.Get() found = %v, want %v", found, tt.found)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedMapSet(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
key string
|
||||||
|
existing NestedMap
|
||||||
|
want NestedMap
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Set a value in a nested map",
|
||||||
|
key: "foo.bar.baz",
|
||||||
|
existing: NestedMap{},
|
||||||
|
want: NestedMap{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"baz": "qux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Overwrite existing value",
|
||||||
|
key: "foo.bar",
|
||||||
|
existing: NestedMap{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "old",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: NestedMap{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "qux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set a value overwriting a primitive with a nested map",
|
||||||
|
key: "foo.bar",
|
||||||
|
existing: NestedMap{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
want: NestedMap{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "qux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.existing.Set(tt.key, "qux")
|
||||||
|
if !reflect.DeepEqual(tt.existing, tt.want) {
|
||||||
|
t.Errorf("NestedMap.Set() got = %v, want %v", tt.existing, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeMaps(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dest map[string]interface{}
|
||||||
|
src map[string]interface{}
|
||||||
|
result map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Merge two maps",
|
||||||
|
dest: map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
src: map[string]interface{}{
|
||||||
|
"baz": "qux",
|
||||||
|
},
|
||||||
|
result: map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Merge two maps with overlapping keys",
|
||||||
|
dest: map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qux",
|
||||||
|
},
|
||||||
|
src: map[string]interface{}{
|
||||||
|
"baz": "quux",
|
||||||
|
},
|
||||||
|
result: map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "quux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Merge two maps with overlapping keys and nested maps",
|
||||||
|
dest: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
src: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"qux": "quux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "baz",
|
||||||
|
"qux": "quux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Merge two maps with overlapping keys and nested maps",
|
||||||
|
dest: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
src: map[string]interface{}{
|
||||||
|
"foo": "qux",
|
||||||
|
},
|
||||||
|
result: map[string]interface{}{
|
||||||
|
"foo": "qux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Merge two maps with overlapping keys and nested maps",
|
||||||
|
dest: map[string]interface{}{
|
||||||
|
"foo": "qux",
|
||||||
|
},
|
||||||
|
src: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
MergeMaps(tt.dest, tt.src)
|
||||||
|
if !reflect.DeepEqual(tt.dest, tt.result) {
|
||||||
|
t.Errorf("NestedMap.Set() got = %v, want %v", tt.dest, tt.result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,5 +22,5 @@ extend input SetDefaultFilterInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
configureUI(input: Map!): UIConfig!
|
configureUI(input: Map, partial: Map): UIConfig!
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,12 @@ mutation ConfigureDefaults($input: ConfigDefaultSettingsInput!) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mutation ConfigureUI($input: Map!) {
|
mutation ConfigureUI($input: Map, $partial: Map) {
|
||||||
configureUI(input: $input)
|
configureUI(input: $input, partial: $partial)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation ConfigureUISetting($key: String!, $value: Any) {
|
||||||
|
configureUISetting(key: $key, value: $value)
|
||||||
}
|
}
|
||||||
|
|
||||||
mutation GenerateAPIKey($input: GenerateAPIKeyInput!) {
|
mutation GenerateAPIKey($input: GenerateAPIKeyInput!) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import {
|
||||||
mutateMetadataScan,
|
mutateMetadataScan,
|
||||||
mutateMetadataAutoTag,
|
mutateMetadataAutoTag,
|
||||||
mutateMetadataGenerate,
|
mutateMetadataGenerate,
|
||||||
useConfigureDefaults,
|
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { withoutTypename } from "src/utils/data";
|
import { withoutTypename } from "src/utils/data";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
@ -20,6 +19,7 @@ import { BooleanSetting, Setting, SettingGroup } from "../Inputs";
|
||||||
import { ManualLink } from "src/components/Help/context";
|
import { ManualLink } from "src/components/Help/context";
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { useSettings } from "../context";
|
||||||
|
|
||||||
interface IAutoTagOptions {
|
interface IAutoTagOptions {
|
||||||
options: GQL.AutoTagMetadataInput;
|
options: GQL.AutoTagMetadataInput;
|
||||||
|
|
@ -71,7 +71,9 @@ const AutoTagOptions: React.FC<IAutoTagOptions> = ({
|
||||||
export const LibraryTasks: React.FC = () => {
|
export const LibraryTasks: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const [configureDefaults] = useConfigureDefaults();
|
const { ui, saveUI, loading } = useSettings();
|
||||||
|
|
||||||
|
const { taskDefaults } = ui;
|
||||||
|
|
||||||
const [dialogOpen, setDialogOpenState] = useState({
|
const [dialogOpen, setDialogOpenState] = useState({
|
||||||
scan: false,
|
scan: false,
|
||||||
|
|
@ -111,22 +113,34 @@ export const LibraryTasks: React.FC = () => {
|
||||||
const [configRead, setConfigRead] = useState(false);
|
const [configRead, setConfigRead] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!configuration?.defaults) {
|
if (!configuration?.defaults || loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { scan, autoTag } = configuration.defaults;
|
const { scan, autoTag } = configuration.defaults;
|
||||||
|
|
||||||
if (scan) {
|
// prefer UI defaults over system defaults
|
||||||
|
// other defaults should be deprecated
|
||||||
|
if (taskDefaults?.scan) {
|
||||||
|
setScanOptions(taskDefaults.scan);
|
||||||
|
} else if (scan) {
|
||||||
setScanOptions(withoutTypename(scan));
|
setScanOptions(withoutTypename(scan));
|
||||||
}
|
}
|
||||||
if (autoTag) {
|
|
||||||
|
if (taskDefaults?.autoTag) {
|
||||||
|
setAutoTagOptions(taskDefaults.autoTag);
|
||||||
|
} else if (autoTag) {
|
||||||
setAutoTagOptions(withoutTypename(autoTag));
|
setAutoTagOptions(withoutTypename(autoTag));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (taskDefaults?.generate) {
|
||||||
|
setGenerateOptions(taskDefaults.generate);
|
||||||
|
}
|
||||||
|
|
||||||
// combine the defaults with the system preview generation settings
|
// combine the defaults with the system preview generation settings
|
||||||
// only do this once
|
// only do this once
|
||||||
if (!configRead) {
|
// don't do this if UI had a default
|
||||||
|
if (!configRead && !taskDefaults?.generate) {
|
||||||
if (configuration?.defaults.generate) {
|
if (configuration?.defaults.generate) {
|
||||||
const { generate } = configuration.defaults;
|
const { generate } = configuration.defaults;
|
||||||
setGenerateOptions(withoutTypename(generate));
|
setGenerateOptions(withoutTypename(generate));
|
||||||
|
|
@ -158,7 +172,26 @@ export const LibraryTasks: React.FC = () => {
|
||||||
|
|
||||||
setConfigRead(true);
|
setConfigRead(true);
|
||||||
}
|
}
|
||||||
}, [configuration, configRead]);
|
}, [configuration, configRead, taskDefaults, loading]);
|
||||||
|
|
||||||
|
function configureDefaults(partial: Record<string, {}>) {
|
||||||
|
saveUI({ taskDefaults: { ...partial } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSetScanOptions(s: GQL.ScanMetadataInput) {
|
||||||
|
configureDefaults({ scan: s });
|
||||||
|
setScanOptions(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSetGenerateOptions(s: GQL.GenerateMetadataInput) {
|
||||||
|
configureDefaults({ generate: s });
|
||||||
|
setGenerateOptions(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSetAutoTagOptions(s: GQL.AutoTagMetadataInput) {
|
||||||
|
configureDefaults({ autoTag: s });
|
||||||
|
setAutoTagOptions(s);
|
||||||
|
}
|
||||||
|
|
||||||
function setDialogOpen(s: Partial<DialogOpenState>) {
|
function setDialogOpen(s: Partial<DialogOpenState>) {
|
||||||
setDialogOpenState((v) => {
|
setDialogOpenState((v) => {
|
||||||
|
|
@ -184,14 +217,6 @@ export const LibraryTasks: React.FC = () => {
|
||||||
|
|
||||||
async function runScan(paths?: string[]) {
|
async function runScan(paths?: string[]) {
|
||||||
try {
|
try {
|
||||||
configureDefaults({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
scan: scanOptions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await mutateMetadataScan({
|
await mutateMetadataScan({
|
||||||
...scanOptions,
|
...scanOptions,
|
||||||
paths,
|
paths,
|
||||||
|
|
@ -226,14 +251,6 @@ export const LibraryTasks: React.FC = () => {
|
||||||
|
|
||||||
async function runAutoTag(paths?: string[]) {
|
async function runAutoTag(paths?: string[]) {
|
||||||
try {
|
try {
|
||||||
configureDefaults({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
autoTag: autoTagOptions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await mutateMetadataAutoTag({
|
await mutateMetadataAutoTag({
|
||||||
...autoTagOptions,
|
...autoTagOptions,
|
||||||
paths,
|
paths,
|
||||||
|
|
@ -260,14 +277,6 @@ export const LibraryTasks: React.FC = () => {
|
||||||
|
|
||||||
async function onGenerateClicked() {
|
async function onGenerateClicked() {
|
||||||
try {
|
try {
|
||||||
configureDefaults({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
generate: generateOptions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await mutateMetadataGenerate(generateOptions);
|
await mutateMetadataGenerate(generateOptions);
|
||||||
Toast.success(
|
Toast.success(
|
||||||
intl.formatMessage(
|
intl.formatMessage(
|
||||||
|
|
@ -322,7 +331,7 @@ export const LibraryTasks: React.FC = () => {
|
||||||
}
|
}
|
||||||
collapsible
|
collapsible
|
||||||
>
|
>
|
||||||
<ScanOptions options={scanOptions} setOptions={setScanOptions} />
|
<ScanOptions options={scanOptions} setOptions={onSetScanOptions} />
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|
||||||
|
|
@ -384,7 +393,7 @@ export const LibraryTasks: React.FC = () => {
|
||||||
>
|
>
|
||||||
<AutoTagOptions
|
<AutoTagOptions
|
||||||
options={autoTagOptions}
|
options={autoTagOptions}
|
||||||
setOptions={(o) => setAutoTagOptions(o)}
|
setOptions={onSetAutoTagOptions}
|
||||||
/>
|
/>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|
@ -415,7 +424,7 @@ export const LibraryTasks: React.FC = () => {
|
||||||
>
|
>
|
||||||
<GenerateOptions
|
<GenerateOptions
|
||||||
options={generateOptions}
|
options={generateOptions}
|
||||||
setOptions={setGenerateOptions}
|
setOptions={onSetGenerateOptions}
|
||||||
/>
|
/>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|
|
||||||
|
|
@ -426,12 +426,12 @@ export const SettingsContext: React.FC = ({ children }) => {
|
||||||
type UIConfigInput = GQL.Scalars["Map"]["input"];
|
type UIConfigInput = GQL.Scalars["Map"]["input"];
|
||||||
|
|
||||||
// saves the configuration if no further changes are made after a half second
|
// saves the configuration if no further changes are made after a half second
|
||||||
const saveUIConfig = useDebounce(async (input: IUIConfig) => {
|
const saveUIConfig = useDebounce(async (input: Partial<IUIConfig>) => {
|
||||||
try {
|
try {
|
||||||
setUpdateSuccess(undefined);
|
setUpdateSuccess(undefined);
|
||||||
await updateUIConfig({
|
await updateUIConfig({
|
||||||
variables: {
|
variables: {
|
||||||
input: input as UIConfigInput,
|
partial: input as UIConfigInput,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -461,13 +461,6 @@ export const SettingsContext: React.FC = ({ children }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
setPendingUI((current) => {
|
setPendingUI((current) => {
|
||||||
if (!current) {
|
|
||||||
// use full UI object to ensure nothing is wiped
|
|
||||||
return {
|
|
||||||
...ui,
|
|
||||||
...input,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...current,
|
...current,
|
||||||
...input,
|
...input,
|
||||||
|
|
|
||||||
|
|
@ -2301,9 +2301,36 @@ export const useConfigureDefaults = () =>
|
||||||
update: updateConfiguration,
|
update: updateConfiguration,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function updateUIConfig(
|
||||||
|
cache: ApolloCache<Record<string, StoreObject>>,
|
||||||
|
result: GQL.ConfigureUiMutation["configureUI"] | undefined
|
||||||
|
) {
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
const existing = cache.readQuery<GQL.ConfigurationQuery>({
|
||||||
|
query: GQL.ConfigurationDocument,
|
||||||
|
});
|
||||||
|
|
||||||
|
cache.writeQuery({
|
||||||
|
query: GQL.ConfigurationDocument,
|
||||||
|
data: {
|
||||||
|
configuration: {
|
||||||
|
...existing?.configuration,
|
||||||
|
ui: result,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const useConfigureUI = () =>
|
export const useConfigureUI = () =>
|
||||||
GQL.useConfigureUiMutation({
|
GQL.useConfigureUiMutation({
|
||||||
update: updateConfiguration,
|
update: (cache, result) => updateUIConfig(cache, result.data?.configureUI),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useConfigureUISetting = () =>
|
||||||
|
GQL.useConfigureUiSettingMutation({
|
||||||
|
update: (cache, result) =>
|
||||||
|
updateUIConfig(cache, result.data?.configureUISetting),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useConfigureScraping = () =>
|
export const useConfigureScraping = () =>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue