mirror of
https://github.com/stashapp/stash.git
synced 2025-12-07 17:02:38 +01:00
Use existing formats for saved filters (#5697)
* Use existing formats for saved filters * Fix date criterion marshalling
This commit is contained in:
parent
ea5073fef4
commit
fdb2dd9a8b
7 changed files with 218 additions and 72 deletions
|
|
@ -67,7 +67,7 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
|||
mode: filter.mode,
|
||||
name,
|
||||
find_filter: filterCopy.makeFindFilter(),
|
||||
object_filter: filterCopy.makeFilter(),
|
||||
object_filter: filterCopy.makeSavedFilter(),
|
||||
ui_options: filterCopy.makeSavedUIOptions(),
|
||||
},
|
||||
},
|
||||
|
|
@ -142,7 +142,7 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
|||
value: {
|
||||
mode: filter.mode,
|
||||
find_filter: filterCopy.makeFindFilter(),
|
||||
object_filter: filterCopy.makeFilter(),
|
||||
object_filter: filterCopy.makeSavedFilter(),
|
||||
ui_options: filterCopy.makeSavedUIOptions(),
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import {
|
|||
HierarchicalMultiCriterionInput,
|
||||
IntCriterionInput,
|
||||
MultiCriterionInput,
|
||||
DateCriterionInput,
|
||||
TimestampCriterionInput,
|
||||
ConfigDataFragment,
|
||||
DateCriterionInput,
|
||||
} from "src/core/generated-graphql";
|
||||
import TextUtils from "src/utils/text";
|
||||
import {
|
||||
|
|
@ -21,11 +21,13 @@ import {
|
|||
ITimestampValue,
|
||||
ILabeledValueListValue,
|
||||
IPhashDistanceValue,
|
||||
IRangeValue,
|
||||
} from "../types";
|
||||
|
||||
export type Option = string | number | IOptionType;
|
||||
export type CriterionValue =
|
||||
| string
|
||||
| boolean
|
||||
| string[]
|
||||
| ILabeledId[]
|
||||
| IHierarchicalLabelValue
|
||||
|
|
@ -36,7 +38,7 @@ export type CriterionValue =
|
|||
| ITimestampValue
|
||||
| IPhashDistanceValue;
|
||||
|
||||
export interface ISavedCriterion<T extends CriterionValue> {
|
||||
export interface ISavedCriterion<T> {
|
||||
modifier: CriterionModifier;
|
||||
value: T | undefined;
|
||||
}
|
||||
|
|
@ -82,9 +84,15 @@ export abstract class Criterion {
|
|||
return `${this.criterionOption.type}`;
|
||||
}
|
||||
|
||||
public abstract toJSON(): string;
|
||||
public abstract toQueryParams(): Record<string, unknown>;
|
||||
|
||||
// fromDecodedParams is used to set the criterion from the query string
|
||||
// i is the decoded parameter object
|
||||
public abstract fromDecodedParams(i: Record<string, unknown>): void;
|
||||
|
||||
public abstract applyToCriterionInput(input: Record<string, unknown>): void;
|
||||
|
||||
public abstract applyToSavedCriterion(input: Record<string, unknown>): void;
|
||||
public abstract setFromSavedCriterion(criterion: unknown): void;
|
||||
}
|
||||
|
||||
|
|
@ -164,38 +172,61 @@ export abstract class ModifierCriterion<
|
|||
);
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
let encodedCriterion;
|
||||
public toQueryParams(): Record<string, unknown> {
|
||||
let encodedCriterion: Record<string, unknown> = {
|
||||
type: this.criterionOption.type,
|
||||
modifier: this.modifier,
|
||||
};
|
||||
|
||||
if (
|
||||
this.modifier === CriterionModifier.IsNull ||
|
||||
this.modifier === CriterionModifier.NotNull
|
||||
this.modifier !== CriterionModifier.IsNull &&
|
||||
this.modifier !== CriterionModifier.NotNull
|
||||
) {
|
||||
encodedCriterion = {
|
||||
type: this.criterionOption.type,
|
||||
modifier: this.modifier,
|
||||
};
|
||||
} else {
|
||||
encodedCriterion = {
|
||||
type: this.criterionOption.type,
|
||||
value: this.value,
|
||||
modifier: this.modifier,
|
||||
};
|
||||
}
|
||||
return JSON.stringify(encodedCriterion);
|
||||
encodedCriterion.value = this.encodeValue();
|
||||
}
|
||||
|
||||
public setFromSavedCriterion(criterion: ISavedCriterion<V>) {
|
||||
if (criterion.value !== undefined && criterion.value !== null) {
|
||||
this.value = criterion.value;
|
||||
return encodedCriterion;
|
||||
}
|
||||
this.modifier = criterion.modifier;
|
||||
|
||||
protected encodeValue(): unknown {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
protected decodeValue(v: unknown) {
|
||||
if (v !== undefined && v !== null) {
|
||||
this.value = v as V;
|
||||
}
|
||||
}
|
||||
|
||||
public fromDecodedParams(i: unknown): void {
|
||||
// use same logic as from saved criterion by default
|
||||
const c = i as ISavedCriterion<V>;
|
||||
this.modifier = c.modifier;
|
||||
this.decodeValue(c.value);
|
||||
}
|
||||
|
||||
public setFromSavedCriterion(criterion: unknown) {
|
||||
const c = criterion as ISavedCriterion<V>;
|
||||
if (c.value !== undefined && c.value !== null) {
|
||||
this.value = c.value;
|
||||
}
|
||||
this.modifier = c.modifier;
|
||||
}
|
||||
|
||||
public applyToCriterionInput(input: Record<string, unknown>) {
|
||||
input[this.criterionOption.type] = this.toCriterionInput();
|
||||
}
|
||||
|
||||
public toCriterionInput(): unknown {
|
||||
// TODO - saved criterion _should_ be criterion input
|
||||
// kicking this can down the road a little further
|
||||
public applyToSavedCriterion(input: Record<string, unknown>): void {
|
||||
input[this.criterionOption.type] = {
|
||||
value: this.value,
|
||||
modifier: this.modifier,
|
||||
};
|
||||
}
|
||||
|
||||
protected toCriterionInput(): unknown {
|
||||
return {
|
||||
value: this.value,
|
||||
modifier: this.modifier,
|
||||
|
|
@ -755,6 +786,33 @@ export function createMandatoryNumberCriterionOption(
|
|||
return new MandatoryNumberCriterionOption(messageID ?? value, value);
|
||||
}
|
||||
|
||||
export function encodeRangeValue<V>(
|
||||
modifier: CriterionModifier,
|
||||
value: IRangeValue<V>
|
||||
): unknown {
|
||||
// only encode value2 if modifier is between/not between
|
||||
if (
|
||||
modifier === CriterionModifier.Between ||
|
||||
modifier === CriterionModifier.NotBetween
|
||||
) {
|
||||
return { value: value.value, value2: value.value2 };
|
||||
}
|
||||
|
||||
return { value: value.value };
|
||||
}
|
||||
|
||||
export function decodeRangeValue<V>(v: {
|
||||
value: V | IRangeValue<V>;
|
||||
value2?: V;
|
||||
}): IRangeValue<V> {
|
||||
// handle backwards compatible value
|
||||
if (typeof v.value === "object") {
|
||||
return v.value as IRangeValue<V>;
|
||||
} else {
|
||||
return { value: v.value, value2: v.value2 };
|
||||
}
|
||||
}
|
||||
|
||||
export class NumberCriterion extends ModifierCriterion<INumberValue> {
|
||||
constructor(type: ModifierCriterionOption) {
|
||||
super(type, { value: undefined, value2: undefined });
|
||||
|
|
@ -787,6 +845,19 @@ export class NumberCriterion extends ModifierCriterion<INumberValue> {
|
|||
};
|
||||
}
|
||||
|
||||
public setFromSavedCriterion(c: {
|
||||
modifier: CriterionModifier;
|
||||
value: number | INumberValue;
|
||||
value2?: number;
|
||||
}) {
|
||||
super.setFromSavedCriterion(c);
|
||||
// this.value = decodeRangeValue(c);
|
||||
}
|
||||
|
||||
protected encodeValue(): unknown {
|
||||
return encodeRangeValue(this.modifier, this.value);
|
||||
}
|
||||
|
||||
protected getLabelValue(_intl: IntlShape) {
|
||||
const { value, value2 } = this.value;
|
||||
if (
|
||||
|
|
@ -867,6 +938,19 @@ export class DurationCriterion extends ModifierCriterion<INumberValue> {
|
|||
};
|
||||
}
|
||||
|
||||
public setFromSavedCriterion(c: {
|
||||
modifier: CriterionModifier;
|
||||
value: number | INumberValue;
|
||||
value2?: number;
|
||||
}) {
|
||||
super.setFromSavedCriterion(c);
|
||||
// this.value = decodeRangeValue(c);
|
||||
}
|
||||
|
||||
protected encodeValue(): unknown {
|
||||
return encodeRangeValue(this.modifier, this.value);
|
||||
}
|
||||
|
||||
protected getLabelValue(_intl: IntlShape) {
|
||||
const value = TextUtils.secondsToTimestamp(this.value.value ?? 0);
|
||||
const value2 = TextUtils.secondsToTimestamp(this.value.value2 ?? 0);
|
||||
|
|
@ -940,17 +1024,23 @@ export class DateCriterion extends ModifierCriterion<IDateValue> {
|
|||
this.value = { ...this.value };
|
||||
}
|
||||
|
||||
public encodeValue() {
|
||||
return {
|
||||
value: this.value.value,
|
||||
value2: this.value.value2,
|
||||
};
|
||||
public setFromSavedCriterion(c: {
|
||||
modifier: CriterionModifier;
|
||||
value: string | IDateValue;
|
||||
value2?: string;
|
||||
}) {
|
||||
super.setFromSavedCriterion(c);
|
||||
// this.value = decodeRangeValue(c);
|
||||
}
|
||||
|
||||
protected encodeValue(): unknown {
|
||||
return encodeRangeValue(this.modifier, this.value);
|
||||
}
|
||||
|
||||
public toCriterionInput(): DateCriterionInput {
|
||||
return {
|
||||
modifier: this.modifier,
|
||||
value: this.value?.value,
|
||||
value: this.value?.value ?? "",
|
||||
value2: this.value?.value2,
|
||||
};
|
||||
}
|
||||
|
|
@ -1043,23 +1133,29 @@ export class TimestampCriterion extends ModifierCriterion<ITimestampValue> {
|
|||
this.value = { ...this.value };
|
||||
}
|
||||
|
||||
public encodeValue() {
|
||||
return {
|
||||
value: this.value?.value,
|
||||
value2: this.value?.value2,
|
||||
};
|
||||
}
|
||||
|
||||
public toCriterionInput(): TimestampCriterionInput {
|
||||
return {
|
||||
modifier: this.modifier,
|
||||
value: this.transformValueToInput(this.value.value),
|
||||
value: this.transformValueToInput(this.value.value ?? ""),
|
||||
value2: this.value.value2
|
||||
? this.transformValueToInput(this.value.value2)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
public setFromSavedCriterion(c: {
|
||||
modifier: CriterionModifier;
|
||||
value: string | ITimestampValue;
|
||||
value2?: string;
|
||||
}) {
|
||||
this.setFromSavedCriterion(c);
|
||||
// this.value = decodeRangeValue(c);
|
||||
}
|
||||
|
||||
protected encodeValue(): unknown {
|
||||
return encodeRangeValue(this.modifier, this.value);
|
||||
}
|
||||
|
||||
protected getLabelValue() {
|
||||
const { value } = this.value;
|
||||
return this.modifier === CriterionModifier.Between ||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ export class CustomFieldsCriterion extends Criterion {
|
|||
input.custom_fields = cloneDeep(this.value);
|
||||
}
|
||||
|
||||
public applyToSavedCriterion(input: Record<string, unknown>): void {
|
||||
input.custom_fields = cloneDeep(this.value);
|
||||
}
|
||||
|
||||
public getLabel(intl: IntlShape): string {
|
||||
// show first criterion
|
||||
if (this.value.length === 0) {
|
||||
|
|
@ -91,19 +95,20 @@ export class CustomFieldsCriterion extends Criterion {
|
|||
);
|
||||
}
|
||||
|
||||
public toJSON(): string {
|
||||
public toQueryParams(): Record<string, unknown> {
|
||||
const encodedCriterion = {
|
||||
type: this.criterionOption.type,
|
||||
value: this.value,
|
||||
};
|
||||
return JSON.stringify(encodedCriterion);
|
||||
return encodedCriterion;
|
||||
}
|
||||
|
||||
public setFromSavedCriterion(criterion: {
|
||||
type: string;
|
||||
value: CustomFieldCriterionInput[];
|
||||
}): void {
|
||||
const { value } = criterion;
|
||||
this.value = cloneDeep(value);
|
||||
public fromDecodedParams(i: unknown): void {
|
||||
const criterion = i as { value: CustomFieldCriterionInput[] };
|
||||
this.value = cloneDeep(criterion.value);
|
||||
}
|
||||
|
||||
public setFromSavedCriterion(input: CustomFieldCriterionInput[]): void {
|
||||
this.value = cloneDeep(input);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ import {
|
|||
IntCriterionInput,
|
||||
} from "src/core/generated-graphql";
|
||||
import { INumberValue } from "../types";
|
||||
import { ModifierCriterion, ModifierCriterionOption } from "./criterion";
|
||||
import {
|
||||
encodeRangeValue,
|
||||
ModifierCriterion,
|
||||
ModifierCriterionOption,
|
||||
} from "./criterion";
|
||||
|
||||
const modifierOptions = [
|
||||
CriterionModifier.Equals,
|
||||
|
|
@ -72,6 +76,19 @@ export class RatingCriterion extends ModifierCriterion<INumberValue> {
|
|||
};
|
||||
}
|
||||
|
||||
public setFromSavedCriterion(c: {
|
||||
modifier: CriterionModifier;
|
||||
value: number | INumberValue;
|
||||
value2?: number;
|
||||
}) {
|
||||
super.setFromSavedCriterion(c);
|
||||
// this.value = decodeRangeValue(c);
|
||||
}
|
||||
|
||||
protected encodeValue(): unknown {
|
||||
return encodeRangeValue(this.modifier, this.value);
|
||||
}
|
||||
|
||||
protected getLabelValue() {
|
||||
const { value, value2 } = this.value;
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ import {
|
|||
StashIdCriterionInput,
|
||||
} from "src/core/generated-graphql";
|
||||
import { IStashIDValue } from "../types";
|
||||
import { ModifierCriterion, ModifierCriterionOption } from "./criterion";
|
||||
import {
|
||||
ISavedCriterion,
|
||||
ModifierCriterion,
|
||||
ModifierCriterionOption,
|
||||
} from "./criterion";
|
||||
|
||||
export const StashIDCriterionOption = new ModifierCriterionOption({
|
||||
messageID: "stash_id",
|
||||
|
|
@ -90,7 +94,29 @@ export class StashIDCriterion extends ModifierCriterion<IStashIDValue> {
|
|||
return ret;
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
public setFromSavedCriterion(
|
||||
criterion: StashIdCriterionInput | ISavedCriterion<StashIdCriterionInput>
|
||||
) {
|
||||
super.setFromSavedCriterion(criterion);
|
||||
|
||||
// const asStashIDValue = criterion as StashIdCriterionInput;
|
||||
// const asSavedCriterion =
|
||||
// criterion as ISavedCriterion<StashIdCriterionInput>;
|
||||
// if (asStashIDValue.endpoint || asStashIDValue.stash_id) {
|
||||
// this.value = {
|
||||
// endpoint: asStashIDValue.endpoint ?? "",
|
||||
// stashID: asStashIDValue.stash_id ?? "",
|
||||
// };
|
||||
// } else if (asSavedCriterion.value) {
|
||||
// this.value = {
|
||||
// endpoint: asSavedCriterion.value.endpoint ?? "",
|
||||
// stashID: asSavedCriterion.value.stash_id ?? "",
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
||||
public toQueryParams(): Record<string, unknown> {
|
||||
super.toQueryParams();
|
||||
let encodedCriterion;
|
||||
if (
|
||||
(this.modifier === CriterionModifier.IsNull ||
|
||||
|
|
@ -108,7 +134,7 @@ export class StashIDCriterion extends ModifierCriterion<IStashIDValue> {
|
|||
modifier: this.modifier,
|
||||
};
|
||||
}
|
||||
return JSON.stringify(encodedCriterion);
|
||||
return encodedCriterion;
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,7 @@ import {
|
|||
SavedFilterDataFragment,
|
||||
SortDirectionEnum,
|
||||
} from "src/core/generated-graphql";
|
||||
import {
|
||||
Criterion,
|
||||
CriterionValue,
|
||||
ISavedCriterion,
|
||||
} from "./criteria/criterion";
|
||||
import { Criterion } from "./criteria/criterion";
|
||||
import { getFilterOptions } from "./factory";
|
||||
import { CriterionType, DisplayMode, SavedUIOptions } from "./types";
|
||||
import { ListFilterOptions } from "./filter-options";
|
||||
|
|
@ -159,7 +155,7 @@ export class ListFilterModel {
|
|||
JSON.parse(jsonString);
|
||||
|
||||
const criterion = this.makeCriterion(criterionType);
|
||||
criterion.setFromSavedCriterion(savedCriterion);
|
||||
criterion.fromDecodedParams(savedCriterion);
|
||||
|
||||
this.criteria.push(criterion);
|
||||
} catch (err) {
|
||||
|
|
@ -304,7 +300,7 @@ export class ListFilterModel {
|
|||
if (objectFilter) {
|
||||
for (const [k, v] of Object.entries(objectFilter)) {
|
||||
const criterion = this.makeCriterion(k as CriterionType);
|
||||
criterion.setFromSavedCriterion(v as ISavedCriterion<CriterionValue>);
|
||||
criterion.setFromSavedCriterion(v);
|
||||
this.criteria.push(criterion);
|
||||
}
|
||||
}
|
||||
|
|
@ -335,7 +331,11 @@ export class ListFilterModel {
|
|||
// Returns query parameters with necessary parts URL-encoded
|
||||
public getEncodedParams(): IEncodedParams {
|
||||
const encodedCriteria: string[] = this.criteria.map((criterion) => {
|
||||
let str = ListFilterModel.translateJSON(criterion.toJSON(), false);
|
||||
const queryParams = criterion.toQueryParams();
|
||||
let str = ListFilterModel.translateJSON(
|
||||
JSON.stringify(queryParams),
|
||||
false
|
||||
);
|
||||
|
||||
// URL-encode other characters
|
||||
str = encodeURI(str);
|
||||
|
|
@ -447,6 +447,15 @@ export class ListFilterModel {
|
|||
return output;
|
||||
}
|
||||
|
||||
// TODO - this needs to just use makeFilter, but it needs a migration
|
||||
public makeSavedFilter() {
|
||||
const output: Record<string, unknown> = {};
|
||||
for (const c of this.criteria) {
|
||||
c.applyToSavedCriterion(output);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public makeSavedUIOptions(): SavedUIOptions {
|
||||
return {
|
||||
display_mode: this.displayMode,
|
||||
|
|
|
|||
|
|
@ -39,11 +39,14 @@ export interface IHierarchicalLabelValue {
|
|||
depth: number;
|
||||
}
|
||||
|
||||
export interface INumberValue {
|
||||
value: number | undefined;
|
||||
value2: number | undefined;
|
||||
export interface IRangeValue<V> {
|
||||
value: V | undefined;
|
||||
value2: V | undefined;
|
||||
}
|
||||
|
||||
export type INumberValue = IRangeValue<number>;
|
||||
export type IDateValue = IRangeValue<string>;
|
||||
export type ITimestampValue = IRangeValue<string>;
|
||||
export interface IPHashDuplicationValue {
|
||||
duplicated: boolean;
|
||||
distance?: number; // currently not implemented
|
||||
|
|
@ -54,16 +57,6 @@ export interface IStashIDValue {
|
|||
stashID: string;
|
||||
}
|
||||
|
||||
export interface IDateValue {
|
||||
value: string;
|
||||
value2: string | undefined;
|
||||
}
|
||||
|
||||
export interface ITimestampValue {
|
||||
value: string;
|
||||
value2: string | undefined;
|
||||
}
|
||||
|
||||
export interface IPhashDistanceValue {
|
||||
value: string;
|
||||
distance?: number;
|
||||
|
|
|
|||
Loading…
Reference in a new issue