mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Changes
This commit is contained in:
@@ -74,9 +74,9 @@
|
|||||||
except: ["after-single-line-comment", "first-nested" ],
|
except: ["after-single-line-comment", "first-nested" ],
|
||||||
ignore: ["after-comment"],
|
ignore: ["after-comment"],
|
||||||
}],
|
}],
|
||||||
"selector-max-id": 0,
|
"selector-max-id": 1,
|
||||||
"selector-max-type": 1,
|
"selector-max-type": 2,
|
||||||
"selector-class-pattern": "^(\\.*[A-Z]*[a-z]+)+(-[a-z]+)*$",
|
"selector-class-pattern": "^(\\.*[A-Z]*[a-z]+)+(-[a-z0-9]+)*$",
|
||||||
"selector-combinator-space-after": "always",
|
"selector-combinator-space-after": "always",
|
||||||
"selector-combinator-space-before": "always",
|
"selector-combinator-space-before": "always",
|
||||||
"selector-list-comma-newline-after": "always",
|
"selector-list-comma-newline-after": "always",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { Settings } from "./components/Settings/Settings";
|
|||||||
import { Stats } from "./components/Stats";
|
import { Stats } from "./components/Stats";
|
||||||
import Studios from "./components/Studios/Studios";
|
import Studios from "./components/Studios/Studios";
|
||||||
import { TagList } from "./components/Tags/TagList";
|
import { TagList } from "./components/Tags/TagList";
|
||||||
import { SceneFilenameParser } from "./components/scenes/SceneFilenameParser";
|
import { SceneFilenameParser } from "./components/SceneFilenameParser/SceneFilenameParser";
|
||||||
|
|
||||||
library.add(fas);
|
library.add(fas);
|
||||||
|
|
||||||
|
|||||||
67
ui/v2.5/src/components/SceneFilenameParser/ParserField.ts
Normal file
67
ui/v2.5/src/components/SceneFilenameParser/ParserField.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
|
||||||
|
export class ParserField {
|
||||||
|
public field: string;
|
||||||
|
public helperText?: string;
|
||||||
|
|
||||||
|
constructor(field: string, helperText?: string) {
|
||||||
|
this.field = field;
|
||||||
|
this.helperText = helperText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFieldPattern() {
|
||||||
|
return `{${this.field}}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Title = new ParserField("title");
|
||||||
|
static Ext = new ParserField("ext", "File extension");
|
||||||
|
|
||||||
|
static I = new ParserField("i", "Matches any ignored word");
|
||||||
|
static D = new ParserField("d", "Matches any delimiter (.-_)");
|
||||||
|
|
||||||
|
static Performer = new ParserField("performer");
|
||||||
|
static Studio = new ParserField("studio");
|
||||||
|
static Tag = new ParserField("tag");
|
||||||
|
|
||||||
|
// date fields
|
||||||
|
static Date = new ParserField("date", "YYYY-MM-DD");
|
||||||
|
static YYYY = new ParserField("yyyy", "Year");
|
||||||
|
static YY = new ParserField("yy", "Year (20YY)");
|
||||||
|
static MM = new ParserField("mm", "Two digit month");
|
||||||
|
static DD = new ParserField("dd", "Two digit date");
|
||||||
|
static YYYYMMDD = new ParserField("yyyymmdd");
|
||||||
|
static YYMMDD = new ParserField("yymmdd");
|
||||||
|
static DDMMYYYY = new ParserField("ddmmyyyy");
|
||||||
|
static DDMMYY = new ParserField("ddmmyy");
|
||||||
|
static MMDDYYYY = new ParserField("mmddyyyy");
|
||||||
|
static MMDDYY = new ParserField("mmddyy");
|
||||||
|
|
||||||
|
static validFields = [
|
||||||
|
ParserField.Title,
|
||||||
|
ParserField.Ext,
|
||||||
|
ParserField.D,
|
||||||
|
ParserField.I,
|
||||||
|
ParserField.Performer,
|
||||||
|
ParserField.Studio,
|
||||||
|
ParserField.Tag,
|
||||||
|
ParserField.Date,
|
||||||
|
ParserField.YYYY,
|
||||||
|
ParserField.YY,
|
||||||
|
ParserField.MM,
|
||||||
|
ParserField.DD,
|
||||||
|
ParserField.YYYYMMDD,
|
||||||
|
ParserField.YYMMDD,
|
||||||
|
ParserField.DDMMYYYY,
|
||||||
|
ParserField.DDMMYY,
|
||||||
|
ParserField.MMDDYYYY,
|
||||||
|
ParserField.MMDDYY
|
||||||
|
];
|
||||||
|
|
||||||
|
static fullDateFields = [
|
||||||
|
ParserField.YYYYMMDD,
|
||||||
|
ParserField.YYMMDD,
|
||||||
|
ParserField.DDMMYYYY,
|
||||||
|
ParserField.DDMMYY,
|
||||||
|
ParserField.MMDDYYYY,
|
||||||
|
ParserField.MMDDYY
|
||||||
|
];
|
||||||
|
}
|
||||||
221
ui/v2.5/src/components/SceneFilenameParser/ParserInput.tsx
Normal file
221
ui/v2.5/src/components/SceneFilenameParser/ParserInput.tsx
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dropdown,
|
||||||
|
DropdownButton,
|
||||||
|
Form,
|
||||||
|
InputGroup,
|
||||||
|
} from 'react-bootstrap';
|
||||||
|
import { ParserField } from './ParserField';
|
||||||
|
import { ShowFields } from './ShowFields';
|
||||||
|
|
||||||
|
const builtInRecipes = [
|
||||||
|
{
|
||||||
|
pattern: "{title}",
|
||||||
|
ignoreWords: [],
|
||||||
|
whitespaceCharacters: "",
|
||||||
|
capitalizeTitle: false,
|
||||||
|
description: "Filename"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "{title}.{ext}",
|
||||||
|
ignoreWords: [],
|
||||||
|
whitespaceCharacters: "",
|
||||||
|
capitalizeTitle: false,
|
||||||
|
description: "Without extension"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "{}.{yy}.{mm}.{dd}.{title}.XXX.{}.{ext}",
|
||||||
|
ignoreWords: [],
|
||||||
|
whitespaceCharacters: ".",
|
||||||
|
capitalizeTitle: true,
|
||||||
|
description: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "{}.{yy}.{mm}.{dd}.{title}.{ext}",
|
||||||
|
ignoreWords: [],
|
||||||
|
whitespaceCharacters: ".",
|
||||||
|
capitalizeTitle: true,
|
||||||
|
description: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "{title}.XXX.{}.{ext}",
|
||||||
|
ignoreWords: [],
|
||||||
|
whitespaceCharacters: ".",
|
||||||
|
capitalizeTitle: true,
|
||||||
|
description: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "{}.{yy}.{mm}.{dd}.{title}.{i}.{ext}",
|
||||||
|
ignoreWords: ["cz", "fr"],
|
||||||
|
whitespaceCharacters: ".",
|
||||||
|
capitalizeTitle: true,
|
||||||
|
description: "Foreign language"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface IParserInput {
|
||||||
|
pattern: string;
|
||||||
|
ignoreWords: string[];
|
||||||
|
whitespaceCharacters: string;
|
||||||
|
capitalizeTitle: boolean;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
findClicked: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IParserRecipe {
|
||||||
|
pattern: string;
|
||||||
|
ignoreWords: string[];
|
||||||
|
whitespaceCharacters: string;
|
||||||
|
capitalizeTitle: boolean;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IParserInputProps {
|
||||||
|
input: IParserInput;
|
||||||
|
onFind: (input: IParserInput) => void;
|
||||||
|
onPageSizeChanged: (newSize: number) => void;
|
||||||
|
showFields: Map<string, boolean>;
|
||||||
|
setShowFields: (fields: Map<string, boolean>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProps) => {
|
||||||
|
const [pattern, setPattern] = useState<string>(props.input.pattern);
|
||||||
|
const [ignoreWords, setIgnoreWords] = useState<string>(
|
||||||
|
props.input.ignoreWords.join(" ")
|
||||||
|
);
|
||||||
|
const [whitespaceCharacters, setWhitespaceCharacters] = useState<string>(
|
||||||
|
props.input.whitespaceCharacters
|
||||||
|
);
|
||||||
|
const [capitalizeTitle, setCapitalizeTitle] = useState<boolean>(
|
||||||
|
props.input.capitalizeTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
function onFind() {
|
||||||
|
props.onFind({
|
||||||
|
pattern,
|
||||||
|
ignoreWords: ignoreWords.split(" "),
|
||||||
|
whitespaceCharacters,
|
||||||
|
capitalizeTitle,
|
||||||
|
page: 1,
|
||||||
|
pageSize: props.input.pageSize,
|
||||||
|
findClicked: props.input.findClicked
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setParserRecipe(recipe: IParserRecipe) {
|
||||||
|
setPattern(recipe.pattern);
|
||||||
|
setIgnoreWords(recipe.ignoreWords.join(" "));
|
||||||
|
setWhitespaceCharacters(recipe.whitespaceCharacters);
|
||||||
|
setCapitalizeTitle(recipe.capitalizeTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validFields = [new ParserField("", "Wildcard")].concat(
|
||||||
|
ParserField.validFields
|
||||||
|
);
|
||||||
|
|
||||||
|
function addParserField(field: ParserField) {
|
||||||
|
setPattern(pattern + field.getFieldPattern());
|
||||||
|
}
|
||||||
|
|
||||||
|
const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Group className="row">
|
||||||
|
<Form.Label htmlFor="filename-pattern" className="col-2">Filename Pattern</Form.Label>
|
||||||
|
<InputGroup className="col-8">
|
||||||
|
<Form.Control
|
||||||
|
id="filename-pattern"
|
||||||
|
onChange={(newValue: any) => setPattern(newValue.target.value)}
|
||||||
|
value={pattern}
|
||||||
|
/>
|
||||||
|
<InputGroup.Append>
|
||||||
|
<DropdownButton id="parser-field-select" title="Add Field">
|
||||||
|
{validFields.map(item => (
|
||||||
|
<Dropdown.Item onSelect={() => addParserField(item)}>
|
||||||
|
<span>{item.field}</span>
|
||||||
|
<span className="ml-auto">{item.helperText}</span>
|
||||||
|
</Dropdown.Item>
|
||||||
|
))}
|
||||||
|
</DropdownButton>
|
||||||
|
</InputGroup.Append>
|
||||||
|
</InputGroup>
|
||||||
|
<Form.Text className="text-muted row col-10 offset-2">Use '\\' to escape literal {} characters</Form.Text>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group className="row" controlId="ignored-words">
|
||||||
|
<Form.Label className="col-2">Ignored words</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
className="col-8"
|
||||||
|
onChange={(newValue: any) => setIgnoreWords(newValue.target.value)}
|
||||||
|
value={ignoreWords}
|
||||||
|
/>
|
||||||
|
<Form.Text className="text-muted col-10 offset-2">Matches with {"{i}"}</Form.Text>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<h5>Title</h5>
|
||||||
|
<Form.Group className="row">
|
||||||
|
<Form.Label htmlFor="whitespace-characters" className="col-2">Whitespace characters:</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
className="col-8"
|
||||||
|
onChange={(newValue: any) =>
|
||||||
|
setWhitespaceCharacters(newValue.target.value)
|
||||||
|
}
|
||||||
|
value={whitespaceCharacters}
|
||||||
|
/>
|
||||||
|
<Form.Text className="text-muted col-10 offset-2">
|
||||||
|
These characters will be replaced with whitespace in the title
|
||||||
|
</Form.Text>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className="row">
|
||||||
|
<Form.Label htmlFor="capitalize-title" className="col-2">Capitalize title</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
className="col-8"
|
||||||
|
type="checkbox"
|
||||||
|
checked={capitalizeTitle}
|
||||||
|
onChange={() => setCapitalizeTitle(!capitalizeTitle)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
{/* TODO - mapping stuff will go here */}
|
||||||
|
|
||||||
|
<Form.Group>
|
||||||
|
<DropdownButton variant="secondary" id="recipe-select" title="Select Parser Recipe">
|
||||||
|
{builtInRecipes.map(item => (
|
||||||
|
<Dropdown.Item onSelect={() => setParserRecipe(item)}>
|
||||||
|
<span>{item.pattern}</span>
|
||||||
|
<span className="mr-auto">{item.description}</span>
|
||||||
|
</Dropdown.Item>
|
||||||
|
))}
|
||||||
|
</DropdownButton>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group>
|
||||||
|
<ShowFields
|
||||||
|
fields={props.showFields}
|
||||||
|
onShowFieldsChanged={fields => props.setShowFields(fields)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group className="row">
|
||||||
|
<Button variant="secondary" className="col-1" onClick={onFind}>Find</Button>
|
||||||
|
<Form.Control
|
||||||
|
as="select"
|
||||||
|
style={{ flexBasis: "min-content" }}
|
||||||
|
options={PAGE_SIZE_OPTIONS}
|
||||||
|
onChange={(event: any) =>
|
||||||
|
props.onPageSizeChanged(parseInt(event.target.value, 10))
|
||||||
|
}
|
||||||
|
defaultValue={props.input.pageSize}
|
||||||
|
className="col-1 filter-item"
|
||||||
|
>
|
||||||
|
{PAGE_SIZE_OPTIONS.map(val => (
|
||||||
|
<option value="val">{val}</option>
|
||||||
|
))}
|
||||||
|
</Form.Control>
|
||||||
|
</Form.Group>
|
||||||
|
</Form.Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,19 +5,18 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Collapse,
|
|
||||||
Dropdown,
|
|
||||||
DropdownButton,
|
|
||||||
Form,
|
Form,
|
||||||
Table
|
Table
|
||||||
} from "react-bootstrap";
|
} from "react-bootstrap";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { StashService } from "src/core/StashService";
|
import { StashService } from "src/core/StashService";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { FilterSelect, Icon, StudioSelect, LoadingIndicator } from "src/components/Shared";
|
import { FilterSelect, StudioSelect, LoadingIndicator } from "src/components/Shared";
|
||||||
import { TextUtils } from "src/utils";
|
import { TextUtils } from "src/utils";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
import { Pagination } from "../list/Pagination";
|
import { Pagination } from "../list/Pagination";
|
||||||
|
import { IParserInput, ParserInput } from './ParserInput';
|
||||||
|
import { ParserField } from './ParserField';
|
||||||
|
|
||||||
class ParserResult<T> {
|
class ParserResult<T> {
|
||||||
public value: GQL.Maybe<T> = null;
|
public value: GQL.Maybe<T> = null;
|
||||||
@@ -37,72 +36,6 @@ class ParserResult<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ParserField {
|
|
||||||
public field: string;
|
|
||||||
public helperText?: string;
|
|
||||||
|
|
||||||
constructor(field: string, helperText?: string) {
|
|
||||||
this.field = field;
|
|
||||||
this.helperText = helperText;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getFieldPattern() {
|
|
||||||
return `{${this.field}}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Title = new ParserField("title");
|
|
||||||
static Ext = new ParserField("ext", "File extension");
|
|
||||||
|
|
||||||
static I = new ParserField("i", "Matches any ignored word");
|
|
||||||
static D = new ParserField("d", "Matches any delimiter (.-_)");
|
|
||||||
|
|
||||||
static Performer = new ParserField("performer");
|
|
||||||
static Studio = new ParserField("studio");
|
|
||||||
static Tag = new ParserField("tag");
|
|
||||||
|
|
||||||
// date fields
|
|
||||||
static Date = new ParserField("date", "YYYY-MM-DD");
|
|
||||||
static YYYY = new ParserField("yyyy", "Year");
|
|
||||||
static YY = new ParserField("yy", "Year (20YY)");
|
|
||||||
static MM = new ParserField("mm", "Two digit month");
|
|
||||||
static DD = new ParserField("dd", "Two digit date");
|
|
||||||
static YYYYMMDD = new ParserField("yyyymmdd");
|
|
||||||
static YYMMDD = new ParserField("yymmdd");
|
|
||||||
static DDMMYYYY = new ParserField("ddmmyyyy");
|
|
||||||
static DDMMYY = new ParserField("ddmmyy");
|
|
||||||
static MMDDYYYY = new ParserField("mmddyyyy");
|
|
||||||
static MMDDYY = new ParserField("mmddyy");
|
|
||||||
|
|
||||||
static validFields = [
|
|
||||||
ParserField.Title,
|
|
||||||
ParserField.Ext,
|
|
||||||
ParserField.D,
|
|
||||||
ParserField.I,
|
|
||||||
ParserField.Performer,
|
|
||||||
ParserField.Studio,
|
|
||||||
ParserField.Tag,
|
|
||||||
ParserField.Date,
|
|
||||||
ParserField.YYYY,
|
|
||||||
ParserField.YY,
|
|
||||||
ParserField.MM,
|
|
||||||
ParserField.DD,
|
|
||||||
ParserField.YYYYMMDD,
|
|
||||||
ParserField.YYMMDD,
|
|
||||||
ParserField.DDMMYYYY,
|
|
||||||
ParserField.DDMMYY,
|
|
||||||
ParserField.MMDDYYYY,
|
|
||||||
ParserField.MMDDYY
|
|
||||||
];
|
|
||||||
|
|
||||||
static fullDateFields = [
|
|
||||||
ParserField.YYYYMMDD,
|
|
||||||
ParserField.YYMMDD,
|
|
||||||
ParserField.DDMMYYYY,
|
|
||||||
ParserField.DDMMYY,
|
|
||||||
ParserField.MMDDYYYY,
|
|
||||||
ParserField.MMDDYY
|
|
||||||
];
|
|
||||||
}
|
|
||||||
class SceneParserResult {
|
class SceneParserResult {
|
||||||
public id: string;
|
public id: string;
|
||||||
public filename: string;
|
public filename: string;
|
||||||
@@ -219,69 +152,6 @@ class SceneParserResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IParserInput {
|
|
||||||
pattern: string;
|
|
||||||
ignoreWords: string[];
|
|
||||||
whitespaceCharacters: string;
|
|
||||||
capitalizeTitle: boolean;
|
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
findClicked: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IParserRecipe {
|
|
||||||
pattern: string;
|
|
||||||
ignoreWords: string[];
|
|
||||||
whitespaceCharacters: string;
|
|
||||||
capitalizeTitle: boolean;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const builtInRecipes = [
|
|
||||||
{
|
|
||||||
pattern: "{title}",
|
|
||||||
ignoreWords: [],
|
|
||||||
whitespaceCharacters: "",
|
|
||||||
capitalizeTitle: false,
|
|
||||||
description: "Filename"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "{title}.{ext}",
|
|
||||||
ignoreWords: [],
|
|
||||||
whitespaceCharacters: "",
|
|
||||||
capitalizeTitle: false,
|
|
||||||
description: "Without extension"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "{}.{yy}.{mm}.{dd}.{title}.XXX.{}.{ext}",
|
|
||||||
ignoreWords: [],
|
|
||||||
whitespaceCharacters: ".",
|
|
||||||
capitalizeTitle: true,
|
|
||||||
description: ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "{}.{yy}.{mm}.{dd}.{title}.{ext}",
|
|
||||||
ignoreWords: [],
|
|
||||||
whitespaceCharacters: ".",
|
|
||||||
capitalizeTitle: true,
|
|
||||||
description: ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "{title}.XXX.{}.{ext}",
|
|
||||||
ignoreWords: [],
|
|
||||||
whitespaceCharacters: ".",
|
|
||||||
capitalizeTitle: true,
|
|
||||||
description: ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "{}.{yy}.{mm}.{dd}.{title}.{i}.{ext}",
|
|
||||||
ignoreWords: ["cz", "fr"],
|
|
||||||
whitespaceCharacters: ".",
|
|
||||||
capitalizeTitle: true,
|
|
||||||
description: "Foreign language"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const initialParserInput = {
|
const initialParserInput = {
|
||||||
pattern: "{title}.{ext}",
|
pattern: "{title}.{ext}",
|
||||||
ignoreWords: [],
|
ignoreWords: [],
|
||||||
@@ -518,181 +388,6 @@ export const SceneFilenameParser: React.FC = () => {
|
|||||||
setAllStudioSet(selected);
|
setAllStudioSet(selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IShowFieldsProps {
|
|
||||||
fields: Map<string, boolean>;
|
|
||||||
onShowFieldsChanged: (fields: Map<string, boolean>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ShowFields(props: IShowFieldsProps) {
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
function handleClick(label: string) {
|
|
||||||
const copy = new Map<string, boolean>(props.fields);
|
|
||||||
copy.set(label, !props.fields.get(label));
|
|
||||||
props.onShowFieldsChanged(copy);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldRows = [...props.fields.entries()].map(([label, enabled]) => (
|
|
||||||
<div
|
|
||||||
key={label}
|
|
||||||
onClick={() => {
|
|
||||||
handleClick(label);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon={enabled ? "check" : "times"} />
|
|
||||||
<span>{label}</span>
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div onClick={() => setOpen(!open)}>
|
|
||||||
<Icon icon={open ? "chevron-down" : "chevron-right"} />
|
|
||||||
<span>Display fields</span>
|
|
||||||
</div>
|
|
||||||
<Collapse in={open}>
|
|
||||||
<div>{fieldRows}</div>
|
|
||||||
</Collapse>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IParserInputProps {
|
|
||||||
input: IParserInput;
|
|
||||||
onFind: (input: IParserInput) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ParserInput(props: IParserInputProps) {
|
|
||||||
const [pattern, setPattern] = useState<string>(props.input.pattern);
|
|
||||||
const [ignoreWords, setIgnoreWords] = useState<string>(
|
|
||||||
props.input.ignoreWords.join(" ")
|
|
||||||
);
|
|
||||||
const [whitespaceCharacters, setWhitespaceCharacters] = useState<string>(
|
|
||||||
props.input.whitespaceCharacters
|
|
||||||
);
|
|
||||||
const [capitalizeTitle, setCapitalizeTitle] = useState<boolean>(
|
|
||||||
props.input.capitalizeTitle
|
|
||||||
);
|
|
||||||
|
|
||||||
function onFind() {
|
|
||||||
props.onFind({
|
|
||||||
pattern,
|
|
||||||
ignoreWords: ignoreWords.split(" "),
|
|
||||||
whitespaceCharacters,
|
|
||||||
capitalizeTitle,
|
|
||||||
page: 1,
|
|
||||||
pageSize: props.input.pageSize,
|
|
||||||
findClicked: props.input.findClicked
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setParserRecipe(recipe: IParserRecipe) {
|
|
||||||
setPattern(recipe.pattern);
|
|
||||||
setIgnoreWords(recipe.ignoreWords.join(" "));
|
|
||||||
setWhitespaceCharacters(recipe.whitespaceCharacters);
|
|
||||||
setCapitalizeTitle(recipe.capitalizeTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
const validFields = [new ParserField("", "Wildcard")].concat(
|
|
||||||
ParserField.validFields
|
|
||||||
);
|
|
||||||
|
|
||||||
function addParserField(field: ParserField) {
|
|
||||||
setPattern(pattern + field.getFieldPattern());
|
|
||||||
}
|
|
||||||
|
|
||||||
const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120"];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Control
|
|
||||||
onChange={(newValue: any) => setPattern(newValue.target.value)}
|
|
||||||
value={pattern}
|
|
||||||
/>
|
|
||||||
<DropdownButton id="parser-field-select" title="Add Field">
|
|
||||||
{validFields.map(item => (
|
|
||||||
<Dropdown.Item onSelect={() => addParserField(item)}>
|
|
||||||
<span>{item.field}</span>
|
|
||||||
<span className="ml-auto">{item.helperText}</span>
|
|
||||||
</Dropdown.Item>
|
|
||||||
))}
|
|
||||||
</DropdownButton>
|
|
||||||
<div>Use '\\' to escape literal {} characters</div>
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Label>Ignored words::</Form.Label>
|
|
||||||
<Form.Control
|
|
||||||
onChange={(newValue: any) => setIgnoreWords(newValue.target.value)}
|
|
||||||
value={ignoreWords}
|
|
||||||
/>
|
|
||||||
<div>Matches with {"{i}"}</div>
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group>
|
|
||||||
<h5>Title</h5>
|
|
||||||
<Form.Label>Whitespace characters:</Form.Label>
|
|
||||||
<Form.Control
|
|
||||||
onChange={(newValue: any) =>
|
|
||||||
setWhitespaceCharacters(newValue.target.value)
|
|
||||||
}
|
|
||||||
value={whitespaceCharacters}
|
|
||||||
/>
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Label>Capitalize title</Form.Label>
|
|
||||||
<Form.Control
|
|
||||||
type="checkbox"
|
|
||||||
checked={capitalizeTitle}
|
|
||||||
onChange={() => setCapitalizeTitle(!capitalizeTitle)}
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
<div>
|
|
||||||
These characters will be replaced with whitespace in the title
|
|
||||||
</div>
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
{/* TODO - mapping stuff will go here */}
|
|
||||||
|
|
||||||
<Form.Group>
|
|
||||||
<DropdownButton id="recipe-select" title="Select Parser Recipe">
|
|
||||||
{builtInRecipes.map(item => (
|
|
||||||
<Dropdown.Item onSelect={() => setParserRecipe(item)}>
|
|
||||||
<span>{item.pattern}</span>
|
|
||||||
<span className="mr-auto">{item.description}</span>
|
|
||||||
</Dropdown.Item>
|
|
||||||
))}
|
|
||||||
</DropdownButton>
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group>
|
|
||||||
<ShowFields
|
|
||||||
fields={showFields}
|
|
||||||
onShowFieldsChanged={fields => setShowFields(fields)}
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group>
|
|
||||||
<Button onClick={onFind}>Find</Button>
|
|
||||||
<Form.Control
|
|
||||||
as="select"
|
|
||||||
style={{ flexBasis: "min-content" }}
|
|
||||||
options={PAGE_SIZE_OPTIONS}
|
|
||||||
onChange={(event: any) =>
|
|
||||||
onPageSizeChanged(parseInt(event.target.value, 10))
|
|
||||||
}
|
|
||||||
defaultValue={props.input.pageSize}
|
|
||||||
className="filter-item"
|
|
||||||
>
|
|
||||||
{PAGE_SIZE_OPTIONS.map(val => (
|
|
||||||
<option value="val">{val}</option>
|
|
||||||
))}
|
|
||||||
</Form.Control>
|
|
||||||
</Form.Group>
|
|
||||||
</Form.Group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ISceneParserFieldProps {
|
interface ISceneParserFieldProps {
|
||||||
parserResult: ParserResult<any>;
|
parserResult: ParserResult<any>;
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -1062,7 +757,13 @@ export const SceneFilenameParser: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Card id="parser-container">
|
<Card id="parser-container">
|
||||||
<h4>Scene Filename Parser</h4>
|
<h4>Scene Filename Parser</h4>
|
||||||
<ParserInput input={parserInput} onFind={input => onFindClicked(input)} />
|
<ParserInput
|
||||||
|
input={parserInput}
|
||||||
|
onFind={input => onFindClicked(input)}
|
||||||
|
onPageSizeChanged={onPageSizeChanged}
|
||||||
|
showFields={showFields}
|
||||||
|
setShowFields={setShowFields}
|
||||||
|
/>
|
||||||
|
|
||||||
{isLoading && <LoadingIndicator />}
|
{isLoading && <LoadingIndicator />}
|
||||||
{renderTable()}
|
{renderTable()}
|
||||||
45
ui/v2.5/src/components/SceneFilenameParser/ShowFields.tsx
Normal file
45
ui/v2.5/src/components/SceneFilenameParser/ShowFields.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Collapse
|
||||||
|
} from 'react-bootstrap';
|
||||||
|
import { Icon } from 'src/components/Shared';
|
||||||
|
|
||||||
|
interface IShowFieldsProps {
|
||||||
|
fields: Map<string, boolean>;
|
||||||
|
onShowFieldsChanged: (fields: Map<string, boolean>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShowFields = (props: IShowFieldsProps) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
function handleClick(label: string) {
|
||||||
|
const copy = new Map<string, boolean>(props.fields);
|
||||||
|
copy.set(label, !props.fields.get(label));
|
||||||
|
props.onShowFieldsChanged(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldRows = [...props.fields.entries()].map(([label, enabled]) => (
|
||||||
|
<div
|
||||||
|
key={label}
|
||||||
|
onClick={() => {
|
||||||
|
handleClick(label);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon={enabled ? "check" : "times"} />
|
||||||
|
<span>{label}</span>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button onClick={() => setOpen(!open)} className="minimal">
|
||||||
|
<Icon icon={open ? "chevron-down" : "chevron-right"} />
|
||||||
|
<span>Display fields</span>
|
||||||
|
</Button>
|
||||||
|
<Collapse in={open}>
|
||||||
|
<div>{fieldRows}</div>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -62,9 +62,10 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.Group className={props.className}>
|
<Form.Group className={`duration-input ${props.className}`}>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
|
className="duration-control"
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e: any) => setValue(e.target.value)}
|
onChange={(e: any) => setValue(e.target.value)}
|
||||||
|
|||||||
@@ -239,9 +239,11 @@ export const TagSelect: React.FC<IFilterProps> = props => {
|
|||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const placeholder = props.noSelectionString ?? "Select tags...";
|
const placeholder = props.noSelectionString ?? "Select tags...";
|
||||||
|
|
||||||
|
const selectedTags = props.ids ?? selectedIds;
|
||||||
|
|
||||||
const tags = data?.allTags ?? [];
|
const tags = data?.allTags ?? [];
|
||||||
const selected = tags
|
const selected = tags
|
||||||
.filter(tag => selectedIds.indexOf(tag.id) !== -1)
|
.filter(tag => selectedTags.indexOf(tag.id) !== -1)
|
||||||
.map(tag => ({ value: tag.id, label: tag.name }));
|
.map(tag => ({ value: tag.id, label: tag.name }));
|
||||||
const items: Option[] = tags.map(item => ({
|
const items: Option[] = tags.map(item => ({
|
||||||
value: item.id,
|
value: item.id,
|
||||||
|
|||||||
@@ -40,12 +40,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.duration-button {
|
.duration-input {
|
||||||
|
.duration-control {
|
||||||
|
min-width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-button {
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
line-height: 10px;
|
line-height: 10px;
|
||||||
margin-left: 0 !important;
|
|
||||||
padding: 1px 7px;
|
padding: 1px 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn + .btn {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-list {
|
.folder-list {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
#tag-list-container {
|
#tag-list-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -24,5 +23,3 @@
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,17 +85,19 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wall-item video,
|
.wall {
|
||||||
.wall-item img {
|
.wall-item {
|
||||||
|
line-height: 0;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
width: 20%;
|
||||||
|
|
||||||
|
video,
|
||||||
|
img {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.wall-item {
|
|
||||||
line-height: 0;
|
|
||||||
overflow: visible;
|
|
||||||
padding: 0 !important;
|
|
||||||
position: relative;
|
|
||||||
width: 20%;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
|||||||
if (props.onChangeZoom) {
|
if (props.onChangeZoom) {
|
||||||
return (
|
return (
|
||||||
<Form.Control
|
<Form.Control
|
||||||
className="zoom-slider"
|
className="zoom-slider col-1"
|
||||||
type="range"
|
type="range"
|
||||||
min={0}
|
min={0}
|
||||||
max={3}
|
max={3}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.performer.image {
|
.performer.image {
|
||||||
background-position: center !important;
|
background-position: center;
|
||||||
background-repeat: no-repeat !important;
|
background-repeat: no-repeat;
|
||||||
background-size: cover !important;
|
background-size: cover;
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
@@ -20,10 +20,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#url-field {
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrape-url-button {
|
.scrape-url-button {
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
float: right;
|
float: right;
|
||||||
@@ -38,6 +34,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#url-field {
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
#performer-page {
|
#performer-page {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
@@ -57,11 +57,11 @@
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
|
||||||
.not-favorite {
|
.not-favorite {
|
||||||
color: rgba(191, 204, 214, .5) !important;
|
color: rgba(191, 204, 214, .5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite {
|
.favorite {
|
||||||
color: #ff7373 !important;
|
color: #ff7373;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
<HoverPopover placement="bottom" content={popoverContent}>
|
<HoverPopover placement="bottom" content={popoverContent}>
|
||||||
<Button className="minimal">
|
<Button className="minimal">
|
||||||
<Icon icon="tag" />
|
<Icon icon="tag" />
|
||||||
{props.scene.tags.length}
|
<span>{props.scene.tags.length}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</HoverPopover>
|
</HoverPopover>
|
||||||
);
|
);
|
||||||
@@ -117,7 +117,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
<HoverPopover placement="bottom" content={popoverContent}>
|
<HoverPopover placement="bottom" content={popoverContent}>
|
||||||
<Button className="minimal">
|
<Button className="minimal">
|
||||||
<Icon icon="user" />
|
<Icon icon="user" />
|
||||||
{props.scene.performers.length}
|
<span>{props.scene.performers.length}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</HoverPopover>
|
</HoverPopover>
|
||||||
);
|
);
|
||||||
@@ -135,7 +135,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
<HoverPopover placement="bottom" content={popoverContent}>
|
<HoverPopover placement="bottom" content={popoverContent}>
|
||||||
<Button className="minimal">
|
<Button className="minimal">
|
||||||
<Icon icon="map-marker-alt" />
|
<Icon icon="map-marker-alt" />
|
||||||
{props.scene.scene_markers.length}
|
<span>{props.scene.scene_markers.length}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</HoverPopover>
|
</HoverPopover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -128,4 +128,3 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -180,58 +180,50 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateScenesEditState(state: GQL.SlimSceneDataFragment[]) {
|
useEffect(() => {
|
||||||
|
const state = props.selected;
|
||||||
let updateRating = "";
|
let updateRating = "";
|
||||||
let updateStudioId: string|undefined;
|
let updateStudioID: string|undefined;
|
||||||
let updatePerformerIds: string[] = [];
|
let updatePerformerIds: string[] = [];
|
||||||
let updateTagIds: string[] = [];
|
let updateTagIds: string[] = [];
|
||||||
let first = true;
|
let first = true;
|
||||||
|
|
||||||
state.forEach((scene: GQL.SlimSceneDataFragment) => {
|
state.forEach((scene: GQL.SlimSceneDataFragment) => {
|
||||||
const thisRating = scene.rating?.toString() ?? "";
|
const sceneRating = scene.rating?.toString() ?? "";
|
||||||
const thisStudio = scene?.studio?.id;
|
const sceneStudioID = scene?.studio?.id;
|
||||||
|
const scenePerformerIDs = (scene.performers ?? []).map(p => p.id).sort();
|
||||||
|
const sceneTagIDs = (scene.tags ?? []).map(p => p.id).sort();
|
||||||
|
|
||||||
if (first) {
|
if (first) {
|
||||||
updateRating = thisRating;
|
updateRating = sceneRating;
|
||||||
updateStudioId = thisStudio;
|
updateStudioID = sceneStudioID;
|
||||||
updatePerformerIds = scene.performers
|
updatePerformerIds = scenePerformerIDs;
|
||||||
? scene.performers.map(p => p.id).sort()
|
updateTagIds = sceneTagIDs;
|
||||||
: [];
|
|
||||||
updateTagIds = scene.tags ? scene.tags.map(p => p.id).sort() : [];
|
|
||||||
first = false;
|
first = false;
|
||||||
} else {
|
} else {
|
||||||
if (rating !== thisRating) {
|
if (sceneRating !== updateRating) {
|
||||||
updateRating = "";
|
updateRating = "";
|
||||||
}
|
}
|
||||||
if (studioId !== thisStudio) {
|
if (sceneStudioID !== updateStudioID) {
|
||||||
updateStudioId = undefined;
|
updateStudioID = undefined;
|
||||||
}
|
}
|
||||||
const perfIds = scene.performers
|
if (!_.isEqual(scenePerformerIDs, updatePerformerIds)) {
|
||||||
? scene.performers.map(p => p.id).sort()
|
|
||||||
: [];
|
|
||||||
const tIds = scene.tags ? scene.tags.map(t => t.id).sort() : [];
|
|
||||||
|
|
||||||
if (!_.isEqual(performerIds, perfIds)) {
|
|
||||||
updatePerformerIds = [];
|
updatePerformerIds = [];
|
||||||
}
|
}
|
||||||
|
if (!_.isEqual(sceneTagIDs, updateTagIds)) {
|
||||||
if (!_.isEqual(tagIds, tIds)) {
|
|
||||||
updateTagIds = [];
|
updateTagIds = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setRating(updateRating);
|
setRating(updateRating);
|
||||||
setStudioId(updateStudioId);
|
setStudioId(updateStudioID);
|
||||||
setPerformerIds(updatePerformerIds);
|
setPerformerIds(updatePerformerIds);
|
||||||
setTagIds(updateTagIds);
|
setTagIds(updateTagIds);
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateScenesEditState(props.selected);
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [props.selected]);
|
}, [props.selected]);
|
||||||
|
|
||||||
|
|
||||||
function renderMultiSelect(
|
function renderMultiSelect(
|
||||||
type: "performers" | "tags",
|
type: "performers" | "tags",
|
||||||
ids: string[] | undefined
|
ids: string[] | undefined
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#details {
|
#details {
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
}
|
}
|
||||||
@@ -104,4 +105,3 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,9 @@
|
|||||||
@import "styles/theme";
|
@import "styles/theme";
|
||||||
@import "styles/range";
|
@import "styles/range";
|
||||||
@import "styles/scrollbars";
|
@import "styles/scrollbars";
|
||||||
@import "styles/variables";
|
|
||||||
@import "./components/**/*.scss";
|
@import "./components/**/*.scss";
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family:
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
"Segoe UI",
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
"Fira Sans",
|
|
||||||
"Droid Sans",
|
|
||||||
"Helvetica Neue",
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -70,6 +57,7 @@ code {
|
|||||||
max-height: 11.25rem;
|
max-height: 11.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.zoom-1 {
|
&.zoom-1 {
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
|
|
||||||
@@ -81,6 +69,7 @@ code {
|
|||||||
height: 15rem;
|
height: 15rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.zoom-2 {
|
&.zoom-2 {
|
||||||
width: 30rem;
|
width: 30rem;
|
||||||
|
|
||||||
@@ -92,6 +81,7 @@ code {
|
|||||||
height: 22.5rem;
|
height: 22.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.zoom-3 {
|
&.zoom-3 {
|
||||||
width: 40rem;
|
width: 40rem;
|
||||||
|
|
||||||
@@ -156,7 +146,7 @@ video.preview.portrait {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tag-item {
|
.tag-item {
|
||||||
background-color: #bfccd6;
|
background-color: $muted-gray;
|
||||||
color: #182026;
|
color: #182026;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -214,7 +204,7 @@ video.preview.portrait {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rating-5 {
|
.rating-5 {
|
||||||
background: #FF2F39;
|
background: #ff2f39;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating-4 {
|
.rating-4 {
|
||||||
@@ -224,6 +214,7 @@ video.preview.portrait {
|
|||||||
.rating-3 {
|
.rating-3 {
|
||||||
background: $orange1;
|
background: $orange1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating-2 {
|
.rating-2 {
|
||||||
background: $sepia1;
|
background: $sepia1;
|
||||||
}
|
}
|
||||||
@@ -250,7 +241,7 @@ video.preview.portrait {
|
|||||||
|
|
||||||
.scene-specs-overlay {
|
.scene-specs-overlay {
|
||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
color: #f5f8fa;
|
color: $text-color;
|
||||||
display: block;
|
display: block;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: -.03rem;
|
letter-spacing: -.03rem;
|
||||||
@@ -274,7 +265,7 @@ video.preview.portrait {
|
|||||||
background-position: right top;
|
background-position: right top;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
color: #f5f8fa;
|
color: $text-color;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
letter-spacing: -.03rem;
|
letter-spacing: -.03rem;
|
||||||
@@ -332,20 +323,13 @@ video.preview.portrait {
|
|||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio {
|
/* stylelint-disable selector-class-pattern */
|
||||||
.image {
|
|
||||||
background-position: center !important;
|
|
||||||
background-repeat: no-repeat !important;
|
|
||||||
background-size: contain !important;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-photo-gallery--gallery {
|
.react-photo-gallery--gallery {
|
||||||
img {
|
img {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
#parser-container {
|
#parser-container {
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
@@ -413,11 +397,11 @@ video.preview.portrait {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
color: #f5f8fa;
|
color: $text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
color: #f5f8fa;
|
color: $text-color;
|
||||||
width: inherit;
|
width: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,7 +409,7 @@ video.preview.portrait {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-striped tbody tr:nth-child(odd) td {
|
.table-striped tr:nth-child(odd) td {
|
||||||
background: rgba(92, 112, 128, .15);
|
background: rgba(92, 112, 128, .15);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +439,7 @@ video.preview.portrait {
|
|||||||
.button-link {
|
.button-link {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
color: #48aff0;
|
color: $link-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline;
|
display: inline;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -467,7 +451,7 @@ video.preview.portrait {
|
|||||||
|
|
||||||
.scrubber-button {
|
.scrubber-button {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: #48aff0;
|
color: $link-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrubber-button:hover {
|
.scrubber-button:hover {
|
||||||
@@ -532,6 +516,7 @@ video.preview.portrait {
|
|||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
.svg-inline--fa {
|
.svg-inline--fa {
|
||||||
margin: 0 .4rem;
|
margin: 0 .4rem;
|
||||||
}
|
}
|
||||||
@@ -541,3 +526,4 @@ video.preview.portrait {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* stylelint-enable */
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
/* Blueprint dark theme */
|
/* Blueprint dark theme */
|
||||||
|
|
||||||
$secondary: #394b59;
|
$secondary: #394b59;
|
||||||
|
$muted-gray: #bfccd6;
|
||||||
|
|
||||||
$theme-colors: (
|
$theme-colors: (
|
||||||
primary: #137cbd,
|
primary: #137cbd,
|
||||||
@@ -13,7 +14,7 @@ $theme-colors: (
|
|||||||
);
|
);
|
||||||
|
|
||||||
$body-bg: #202b33;
|
$body-bg: #202b33;
|
||||||
$text-muted: #bfccd6;
|
$text-muted: $muted-gray;
|
||||||
$link-color: #48aff0;
|
$link-color: #48aff0;
|
||||||
$link-hover-color: #48aff0;
|
$link-hover-color: #48aff0;
|
||||||
$text-color: #f5f8fa;
|
$text-color: #f5f8fa;
|
||||||
@@ -23,10 +24,19 @@ $popover-bg: $secondary;
|
|||||||
|
|
||||||
@import "node_modules/bootstrap/scss/bootstrap";
|
@import "node_modules/bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
|
$red1: #a82a2a;
|
||||||
|
$orange1: #a66321;
|
||||||
|
$sepia1: #63411e;
|
||||||
|
$dark-gray2: #202b33;
|
||||||
|
$dark-gray5: #394b59;
|
||||||
|
|
||||||
|
$pt-grid-size: 10px;
|
||||||
|
$pt-navbar-height: 4rem;
|
||||||
|
|
||||||
.btn.active:not(.disabled),
|
.btn.active:not(.disabled),
|
||||||
.btn.active.minimal:not(.disabled) {
|
.btn.active.minimal:not(.disabled) {
|
||||||
background-color: rgba(138, 155, 168, .3);
|
background-color: rgba(138, 155, 168, .3);
|
||||||
color: #f5f8fa;
|
color: $text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.minimal,
|
a.minimal,
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
$red1: #a82a2a;
|
|
||||||
$orange1: #a66321;
|
|
||||||
$sepia1: #63411e;
|
|
||||||
$dark-gray2: #202b33;
|
|
||||||
$dark-gray5: #394b59;
|
|
||||||
|
|
||||||
$pt-grid-size: 10px;
|
|
||||||
$pt-navbar-height: 4rem;
|
|
||||||
Reference in New Issue
Block a user