mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Add SFW content mode option (#6262)
* Use more neutral language for content * Add sfw mode setting * Make configuration context mandatory * Add sfw class when sfw mode active * Hide nsfw performer fields in sfw mode * Hide nsfw sort options * Hide nsfw filter/sort options in sfw mode * Replace o-count with like counter in sfw mode * Use sfw label for o-counter filter in sfw mode * Use likes instead of o-count in sfw mode in other places * Rename sfw mode to sfw content mode * Use sfw image for default performers in sfw mode * Document SFW content mode * Add SFW mode setting to setup * Clarify README * Change wording of sfw mode description * Handle configuration loading error correctly * Hide age in performer cards
This commit is contained in:
parent
bb56b619f5
commit
51999135be
105 changed files with 843 additions and 370 deletions
|
|
@ -9,7 +9,7 @@
|
||||||
[](https://github.com/stashapp/stash/releases/latest)
|
[](https://github.com/stashapp/stash/releases/latest)
|
||||||
[](https://github.com/stashapp/stash/labels/bounty)
|
[](https://github.com/stashapp/stash/labels/bounty)
|
||||||
|
|
||||||
### **Stash is a self-hosted webapp written in Go which organizes and serves your porn.**
|
### **Stash is a self-hosted webapp written in Go which organizes and serves your diverse content collection, catering to both your SFW and NSFW needs.**
|
||||||

|

|
||||||
|
|
||||||
* Stash gathers information about videos in your collection from the internet, and is extensible through the use of community-built plugins for a large number of content producers and sites.
|
* Stash gathers information about videos in your collection from the internet, and is extensible through the use of community-built plugins for a large number of content producers and sites.
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ input SetupInput {
|
||||||
"Empty to indicate $HOME/.stash/config.yml default"
|
"Empty to indicate $HOME/.stash/config.yml default"
|
||||||
configLocation: String!
|
configLocation: String!
|
||||||
stashes: [StashConfigInput!]!
|
stashes: [StashConfigInput!]!
|
||||||
|
"True if SFW content mode is enabled"
|
||||||
|
sfwContentMode: Boolean
|
||||||
"Empty to indicate default"
|
"Empty to indicate default"
|
||||||
databaseFile: String!
|
databaseFile: String!
|
||||||
"Empty to indicate default"
|
"Empty to indicate default"
|
||||||
|
|
@ -341,6 +343,9 @@ type ConfigImageLightboxResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
input ConfigInterfaceInput {
|
input ConfigInterfaceInput {
|
||||||
|
"True if SFW content mode is enabled"
|
||||||
|
sfwContentMode: Boolean
|
||||||
|
|
||||||
"Ordered list of items that should be shown in the menu"
|
"Ordered list of items that should be shown in the menu"
|
||||||
menuItems: [String!]
|
menuItems: [String!]
|
||||||
|
|
||||||
|
|
@ -407,6 +412,9 @@ type ConfigDisableDropdownCreate {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigInterfaceResult {
|
type ConfigInterfaceResult {
|
||||||
|
"True if SFW content mode is enabled"
|
||||||
|
sfwContentMode: Boolean!
|
||||||
|
|
||||||
"Ordered list of items that should be shown in the menu"
|
"Ordered list of items that should be shown in the menu"
|
||||||
menuItems: [String!]
|
menuItems: [String!]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ func initCustomPerformerImages(customPath string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultPerformerImage(name string, gender *models.GenderEnum) []byte {
|
func getDefaultPerformerImage(name string, gender *models.GenderEnum, sfwMode bool) []byte {
|
||||||
// try the custom box first if we have one
|
// try the custom box first if we have one
|
||||||
if performerBoxCustom != nil {
|
if performerBoxCustom != nil {
|
||||||
ret, err := performerBoxCustom.GetRandomImageByName(name)
|
ret, err := performerBoxCustom.GetRandomImageByName(name)
|
||||||
|
|
@ -111,6 +111,10 @@ func getDefaultPerformerImage(name string, gender *models.GenderEnum) []byte {
|
||||||
logger.Warnf("error loading custom default performer image: %v", err)
|
logger.Warnf("error loading custom default performer image: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sfwMode {
|
||||||
|
return static.ReadAll(static.DefaultSFWPerformerImage)
|
||||||
|
}
|
||||||
|
|
||||||
var g models.GenderEnum
|
var g models.GenderEnum
|
||||||
if gender != nil {
|
if gender != nil {
|
||||||
g = *gender
|
g = *gender
|
||||||
|
|
|
||||||
|
|
@ -445,6 +445,8 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
|
||||||
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigInterfaceInput) (*ConfigInterfaceResult, error) {
|
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigInterfaceInput) (*ConfigInterfaceResult, error) {
|
||||||
c := config.GetInstance()
|
c := config.GetInstance()
|
||||||
|
|
||||||
|
r.setConfigBool(config.SFWContentMode, input.SfwContentMode)
|
||||||
|
|
||||||
if input.MenuItems != nil {
|
if input.MenuItems != nil {
|
||||||
c.SetInterface(config.MenuItems, input.MenuItems)
|
c.SetInterface(config.MenuItems, input.MenuItems)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
|
||||||
disableDropdownCreate := config.GetDisableDropdownCreate()
|
disableDropdownCreate := config.GetDisableDropdownCreate()
|
||||||
|
|
||||||
return &ConfigInterfaceResult{
|
return &ConfigInterfaceResult{
|
||||||
|
SfwContentMode: config.GetSFWContentMode(),
|
||||||
MenuItems: menuItems,
|
MenuItems: menuItems,
|
||||||
SoundOnPreview: &soundOnPreview,
|
SoundOnPreview: &soundOnPreview,
|
||||||
WallShowTitle: &wallShowTitle,
|
WallShowTitle: &wallShowTitle,
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,14 @@ type PerformerFinder interface {
|
||||||
GetImage(ctx context.Context, performerID int) ([]byte, error)
|
GetImage(ctx context.Context, performerID int) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sfwConfig interface {
|
||||||
|
GetSFWContentMode() bool
|
||||||
|
}
|
||||||
|
|
||||||
type performerRoutes struct {
|
type performerRoutes struct {
|
||||||
routes
|
routes
|
||||||
performerFinder PerformerFinder
|
performerFinder PerformerFinder
|
||||||
|
sfwConfig sfwConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs performerRoutes) Routes() chi.Router {
|
func (rs performerRoutes) Routes() chi.Router {
|
||||||
|
|
@ -54,7 +59,7 @@ func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(image) == 0 {
|
if len(image) == 0 {
|
||||||
image = getDefaultPerformerImage(performer.Name, performer.Gender)
|
image = getDefaultPerformerImage(performer.Name, performer.Gender, rs.sfwConfig.GetSFWContentMode())
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.ServeImage(w, r, image)
|
utils.ServeImage(w, r, image)
|
||||||
|
|
|
||||||
|
|
@ -322,6 +322,7 @@ func (s *Server) getPerformerRoutes() chi.Router {
|
||||||
return performerRoutes{
|
return performerRoutes{
|
||||||
routes: routes{txnManager: repo.TxnManager},
|
routes: routes{txnManager: repo.TxnManager},
|
||||||
performerFinder: repo.Performer,
|
performerFinder: repo.Performer,
|
||||||
|
sfwConfig: s.manager.Config,
|
||||||
}.Routes()
|
}.Routes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,9 @@ const (
|
||||||
Password = "password"
|
Password = "password"
|
||||||
MaxSessionAge = "max_session_age"
|
MaxSessionAge = "max_session_age"
|
||||||
|
|
||||||
|
// SFWContentMode mode config key
|
||||||
|
SFWContentMode = "sfw_content_mode"
|
||||||
|
|
||||||
FFMpegPath = "ffmpeg_path"
|
FFMpegPath = "ffmpeg_path"
|
||||||
FFProbePath = "ffprobe_path"
|
FFProbePath = "ffprobe_path"
|
||||||
|
|
||||||
|
|
@ -628,7 +631,15 @@ func (i *Config) getStringMapString(key string) map[string]string {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStathPaths returns the configured stash library paths.
|
// GetSFW returns true if SFW mode is enabled.
|
||||||
|
// Default performer images are changed to more agnostic images when enabled.
|
||||||
|
func (i *Config) GetSFWContentMode() bool {
|
||||||
|
i.RLock()
|
||||||
|
defer i.RUnlock()
|
||||||
|
return i.getBool(SFWContentMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStashPaths returns the configured stash library paths.
|
||||||
// Works opposite to the usual case - it will return the override
|
// Works opposite to the usual case - it will return the override
|
||||||
// value only if the main value is not set.
|
// value only if the main value is not set.
|
||||||
func (i *Config) GetStashPaths() StashConfigs {
|
func (i *Config) GetStashPaths() StashConfigs {
|
||||||
|
|
|
||||||
|
|
@ -262,6 +262,10 @@ func (s *Manager) Setup(ctx context.Context, input SetupInput) error {
|
||||||
cfg.SetString(config.Cache, input.CacheLocation)
|
cfg.SetString(config.Cache, input.CacheLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if input.SFWContentMode {
|
||||||
|
cfg.SetBool(config.SFWContentMode, true)
|
||||||
|
}
|
||||||
|
|
||||||
if input.StoreBlobsInDatabase {
|
if input.StoreBlobsInDatabase {
|
||||||
cfg.SetInterface(config.BlobsStorage, config.BlobStorageTypeDatabase)
|
cfg.SetInterface(config.BlobsStorage, config.BlobStorageTypeDatabase)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ type SetupInput struct {
|
||||||
// Empty to indicate $HOME/.stash/config.yml default
|
// Empty to indicate $HOME/.stash/config.yml default
|
||||||
ConfigLocation string `json:"configLocation"`
|
ConfigLocation string `json:"configLocation"`
|
||||||
Stashes []*config.StashConfigInput `json:"stashes"`
|
Stashes []*config.StashConfigInput `json:"stashes"`
|
||||||
|
SFWContentMode bool `json:"sfwContentMode"`
|
||||||
// Empty to indicate default
|
// Empty to indicate default
|
||||||
DatabaseFile string `json:"databaseFile"`
|
DatabaseFile string `json:"databaseFile"`
|
||||||
// Empty to indicate default
|
// Empty to indicate default
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,13 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed performer performer_male scene image gallery tag studio group
|
//go:embed performer performer_male performer_sfw scene image gallery tag studio group
|
||||||
var data embed.FS
|
var data embed.FS
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Performer = "performer"
|
Performer = "performer"
|
||||||
PerformerMale = "performer_male"
|
PerformerMale = "performer_male"
|
||||||
|
DefaultSFWPerformerImage = "performer_sfw/performer.svg"
|
||||||
|
|
||||||
Scene = "scene"
|
Scene = "scene"
|
||||||
DefaultSceneImage = "scene/scene.svg"
|
DefaultSceneImage = "scene/scene.svg"
|
||||||
|
|
|
||||||
7
internal/static/performer_sfw/performer.svg
Normal file
7
internal/static/performer_sfw/performer.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-136 -284 720 1080">
|
||||||
|
<!--!
|
||||||
|
Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2024 Fonticons, Inc.
|
||||||
|
Original from https://github.com/FortAwesome/Font-Awesome/blob/6.x/svgs/solid/user.svg
|
||||||
|
Modified to change color and viewbox
|
||||||
|
-->
|
||||||
|
<path d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512l388.6 0c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304l-91.4 0z" style="fill:#ffffff;fill-opacity:1" /></svg>
|
||||||
|
After Width: | Height: | Size: 645 B |
|
|
@ -71,6 +71,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment ConfigInterfaceData on ConfigInterfaceResult {
|
fragment ConfigInterfaceData on ConfigInterfaceResult {
|
||||||
|
sfwContentMode
|
||||||
menuItems
|
menuItems
|
||||||
soundOnPreview
|
soundOnPreview
|
||||||
wallShowTitle
|
wallShowTitle
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,10 @@ import * as GQL from "./core/generated-graphql";
|
||||||
import { makeTitleProps } from "./hooks/title";
|
import { makeTitleProps } from "./hooks/title";
|
||||||
import { LoadingIndicator } from "./components/Shared/LoadingIndicator";
|
import { LoadingIndicator } from "./components/Shared/LoadingIndicator";
|
||||||
|
|
||||||
import { ConfigurationProvider } from "./hooks/Config";
|
import {
|
||||||
|
ConfigurationProvider,
|
||||||
|
useConfigurationContextOptional,
|
||||||
|
} from "./hooks/Config";
|
||||||
import { ManualProvider } from "./components/Help/context";
|
import { ManualProvider } from "./components/Help/context";
|
||||||
import { InteractiveProvider } from "./hooks/Interactive/context";
|
import { InteractiveProvider } from "./hooks/Interactive/context";
|
||||||
import { ReleaseNotesDialog } from "./components/Dialogs/ReleaseNotesDialog";
|
import { ReleaseNotesDialog } from "./components/Dialogs/ReleaseNotesDialog";
|
||||||
|
|
@ -50,6 +53,7 @@ import { PatchFunction } from "./patch";
|
||||||
|
|
||||||
import moment from "moment/min/moment-with-locales";
|
import moment from "moment/min/moment-with-locales";
|
||||||
import { ErrorMessage } from "./components/Shared/ErrorMessage";
|
import { ErrorMessage } from "./components/Shared/ErrorMessage";
|
||||||
|
import cx from "classnames";
|
||||||
|
|
||||||
const Performers = lazyComponent(
|
const Performers = lazyComponent(
|
||||||
() => import("./components/Performers/Performers")
|
() => import("./components/Performers/Performers")
|
||||||
|
|
@ -104,8 +108,17 @@ const AppContainer: React.FC<React.PropsWithChildren<{}>> = PatchFunction(
|
||||||
) as React.FC;
|
) as React.FC;
|
||||||
|
|
||||||
const MainContainer: React.FC = ({ children }) => {
|
const MainContainer: React.FC = ({ children }) => {
|
||||||
|
// use optional here because the configuration may have be loading or errored
|
||||||
|
const { configuration } = useConfigurationContextOptional() || {};
|
||||||
|
const { sfwContentMode } = configuration?.interface || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`main container-fluid ${appleRendering ? "apple" : ""}`}>
|
<div
|
||||||
|
className={cx("main container-fluid", {
|
||||||
|
apple: appleRendering,
|
||||||
|
"sfw-content-mode": sfwContentMode,
|
||||||
|
})}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -300,28 +313,36 @@ export const App: React.FC = () => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.error) {
|
function renderSimple(content: React.ReactNode) {
|
||||||
return (
|
return (
|
||||||
<IntlProvider
|
<IntlProvider
|
||||||
locale={intlLanguage}
|
locale={intlLanguage}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
formats={intlFormats}
|
formats={intlFormats}
|
||||||
>
|
>
|
||||||
<MainContainer>
|
<MainContainer>{content}</MainContainer>
|
||||||
<ErrorMessage
|
|
||||||
message={
|
|
||||||
<FormattedMessage
|
|
||||||
id="errors.loading_type"
|
|
||||||
values={{ type: "configuration" }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
error={config.error.message}
|
|
||||||
/>
|
|
||||||
</MainContainer>
|
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.loading) {
|
||||||
|
return renderSimple(<LoadingIndicator />);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.error) {
|
||||||
|
return renderSimple(
|
||||||
|
<ErrorMessage
|
||||||
|
message={
|
||||||
|
<FormattedMessage
|
||||||
|
id="errors.loading_type"
|
||||||
|
values={{ type: "configuration" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
error={config.error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<IntlProvider
|
<IntlProvider
|
||||||
|
|
@ -332,10 +353,7 @@ export const App: React.FC = () => {
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<PluginsLoader>
|
<PluginsLoader>
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<ConfigurationProvider
|
<ConfigurationProvider configuration={config.data!.configuration}>
|
||||||
configuration={config.data?.configuration}
|
|
||||||
loading={config.loading}
|
|
||||||
>
|
|
||||||
{maybeRenderReleaseNotes()}
|
{maybeRenderReleaseNotes()}
|
||||||
<ConnectionMonitor />
|
<ConnectionMonitor />
|
||||||
<Suspense fallback={<LoadingIndicator />}>
|
<Suspense fallback={<LoadingIndicator />}>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { Icon } from "src/components/Shared/Icon";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { Manual } from "../Help/Manual";
|
import { Manual } from "../Help/Manual";
|
||||||
import { withoutTypename } from "src/utils/data";
|
import { withoutTypename } from "src/utils/data";
|
||||||
import { GenerateOptions } from "../Settings/Tasks/GenerateOptions";
|
import { GenerateOptions } from "../Settings/Tasks/GenerateOptions";
|
||||||
|
|
@ -25,7 +25,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||||
onClose,
|
onClose,
|
||||||
type,
|
type,
|
||||||
}) => {
|
}) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
function getDefaultOptions(): GQL.GenerateMetadataInput {
|
function getDefaultOptions(): GQL.GenerateMetadataInput {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { useContext, useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { FrontPageContent, ICustomFilter } from "src/core/config";
|
import { FrontPageContent, ICustomFilter } from "src/core/config";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useFindSavedFilter } from "src/core/StashService";
|
import { useFindSavedFilter } from "src/core/StashService";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { GalleryRecommendationRow } from "../Galleries/GalleryRecommendationRow";
|
import { GalleryRecommendationRow } from "../Galleries/GalleryRecommendationRow";
|
||||||
import { ImageRecommendationRow } from "../Images/ImageRecommendationRow";
|
import { ImageRecommendationRow } from "../Images/ImageRecommendationRow";
|
||||||
|
|
@ -105,7 +105,7 @@ interface ISavedFilterResults {
|
||||||
const SavedFilterResults: React.FC<ISavedFilterResults> = ({
|
const SavedFilterResults: React.FC<ISavedFilterResults> = ({
|
||||||
savedFilterID,
|
savedFilterID,
|
||||||
}) => {
|
}) => {
|
||||||
const { configuration: config } = useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
const { loading, data } = useFindSavedFilter(savedFilterID.toString());
|
const { loading, data } = useFindSavedFilter(savedFilterID.toString());
|
||||||
|
|
||||||
const filter = useMemo(() => {
|
const filter = useMemo(() => {
|
||||||
|
|
@ -136,7 +136,7 @@ interface ICustomFilterProps {
|
||||||
const CustomFilterResults: React.FC<ICustomFilterProps> = ({
|
const CustomFilterResults: React.FC<ICustomFilterProps> = ({
|
||||||
customFilter,
|
customFilter,
|
||||||
}) => {
|
}) => {
|
||||||
const { configuration: config } = useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const filter = useMemo(() => {
|
const filter = useMemo(() => {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { Button } from "react-bootstrap";
|
||||||
import { FrontPageConfig } from "./FrontPageConfig";
|
import { FrontPageConfig } from "./FrontPageConfig";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { Control } from "./Control";
|
import { Control } from "./Control";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import {
|
import {
|
||||||
FrontPageContent,
|
FrontPageContent,
|
||||||
generateDefaultFrontPageContent,
|
generateDefaultFrontPageContent,
|
||||||
|
|
@ -24,7 +24,7 @@ const FrontPage: React.FC = PatchComponent("FrontPage", () => {
|
||||||
|
|
||||||
const [saveUI] = useConfigureUI();
|
const [saveUI] = useConfigureUI();
|
||||||
|
|
||||||
const { configuration, loading } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
useScrollToTopOnMount();
|
useScrollToTopOnMount();
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ const FrontPage: React.FC = PatchComponent("FrontPage", () => {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading || saving) {
|
if (saving) {
|
||||||
return <LoadingIndicator />;
|
return <LoadingIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { useFindSavedFilters } from "src/core/StashService";
|
||||||
import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
||||||
import { Button, Form, Modal } from "react-bootstrap";
|
import { Button, Form, Modal } from "react-bootstrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import {
|
import {
|
||||||
ISavedFilterRow,
|
ISavedFilterRow,
|
||||||
ICustomFilter,
|
ICustomFilter,
|
||||||
|
|
@ -277,11 +277,11 @@ interface IFrontPageConfigProps {
|
||||||
export const FrontPageConfig: React.FC<IFrontPageConfigProps> = ({
|
export const FrontPageConfig: React.FC<IFrontPageConfigProps> = ({
|
||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const { configuration, loading } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
const ui = configuration?.ui;
|
const ui = configuration?.ui;
|
||||||
|
|
||||||
const { data: allFilters, loading: loading2 } = useFindSavedFilters();
|
const { data: allFilters, loading } = useFindSavedFilters();
|
||||||
|
|
||||||
const [isAdd, setIsAdd] = useState(false);
|
const [isAdd, setIsAdd] = useState(false);
|
||||||
const [currentContent, setCurrentContent] = useState<FrontPageContent[]>([]);
|
const [currentContent, setCurrentContent] = useState<FrontPageContent[]>([]);
|
||||||
|
|
@ -338,7 +338,7 @@ export const FrontPageConfig: React.FC<IFrontPageConfigProps> = ({
|
||||||
setDragIndex(undefined);
|
setDragIndex(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading || loading2) {
|
if (loading) {
|
||||||
return <LoadingIndicator />;
|
return <LoadingIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { useGalleryDestroy } from "src/core/StashService";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { ModalComponent } from "../Shared/Modal";
|
import { ModalComponent } from "../Shared/Modal";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ export const DeleteGalleriesDialog: React.FC<IDeleteGalleryDialogProps> = (
|
||||||
{ count: props.selected.length, singularEntity, pluralEntity }
|
{ count: props.selected.length, singularEntity, pluralEntity }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
|
|
||||||
const [deleteFile, setDeleteFile] = useState<boolean>(
|
const [deleteFile, setDeleteFile] = useState<boolean>(
|
||||||
config?.defaults.deleteFile ?? false
|
config?.defaults.deleteFile ?? false
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Button, Tab, Nav, Dropdown } from "react-bootstrap";
|
import { Button, Tab, Nav, Dropdown } from "react-bootstrap";
|
||||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
useHistory,
|
useHistory,
|
||||||
Link,
|
Link,
|
||||||
|
|
@ -41,7 +41,7 @@ import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
||||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { useRatingKeybinds } from "src/hooks/keybinds";
|
import { useRatingKeybinds } from "src/hooks/keybinds";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
||||||
import { goBackOrReplace } from "src/utils/history";
|
import { goBackOrReplace } from "src/utils/history";
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ export const GalleryPage: React.FC<IProps> = ({ gallery, add }) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const showLightbox = useGalleryLightbox(gallery.id, gallery.chapters);
|
const showLightbox = useGalleryLightbox(gallery.id, gallery.chapters);
|
||||||
|
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -161,32 +161,38 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="title"
|
||||||
title={intl.formatMessage({ id: "title" })}
|
title={intl.formatMessage({ id: "title" })}
|
||||||
result={title}
|
result={title}
|
||||||
onChange={(value) => setTitle(value)}
|
onChange={(value) => setTitle(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="code"
|
||||||
title={intl.formatMessage({ id: "scene_code" })}
|
title={intl.formatMessage({ id: "scene_code" })}
|
||||||
result={code}
|
result={code}
|
||||||
onChange={(value) => setCode(value)}
|
onChange={(value) => setCode(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStringListRow
|
<ScrapedStringListRow
|
||||||
|
field="urls"
|
||||||
title={intl.formatMessage({ id: "urls" })}
|
title={intl.formatMessage({ id: "urls" })}
|
||||||
result={urls}
|
result={urls}
|
||||||
onChange={(value) => setURLs(value)}
|
onChange={(value) => setURLs(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="date"
|
||||||
title={intl.formatMessage({ id: "date" })}
|
title={intl.formatMessage({ id: "date" })}
|
||||||
placeholder="YYYY-MM-DD"
|
placeholder="YYYY-MM-DD"
|
||||||
result={date}
|
result={date}
|
||||||
onChange={(value) => setDate(value)}
|
onChange={(value) => setDate(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="photographer"
|
||||||
title={intl.formatMessage({ id: "photographer" })}
|
title={intl.formatMessage({ id: "photographer" })}
|
||||||
result={photographer}
|
result={photographer}
|
||||||
onChange={(value) => setPhotographer(value)}
|
onChange={(value) => setPhotographer(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStudioRow
|
<ScrapedStudioRow
|
||||||
|
field="studio"
|
||||||
title={intl.formatMessage({ id: "studios" })}
|
title={intl.formatMessage({ id: "studios" })}
|
||||||
result={studio}
|
result={studio}
|
||||||
onChange={(value) => setStudio(value)}
|
onChange={(value) => setStudio(value)}
|
||||||
|
|
@ -194,6 +200,7 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
||||||
onCreateNew={createNewStudio}
|
onCreateNew={createNewStudio}
|
||||||
/>
|
/>
|
||||||
<ScrapedPerformersRow
|
<ScrapedPerformersRow
|
||||||
|
field="performers"
|
||||||
title={intl.formatMessage({ id: "performers" })}
|
title={intl.formatMessage({ id: "performers" })}
|
||||||
result={performers}
|
result={performers}
|
||||||
onChange={(value) => setPerformers(value)}
|
onChange={(value) => setPerformers(value)}
|
||||||
|
|
@ -203,6 +210,7 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
||||||
/>
|
/>
|
||||||
{scrapedTagsRow}
|
{scrapedTagsRow}
|
||||||
<ScrapedTextAreaRow
|
<ScrapedTextAreaRow
|
||||||
|
field="details"
|
||||||
title={intl.formatMessage({ id: "details" })}
|
title={intl.formatMessage({ id: "details" })}
|
||||||
result={details}
|
result={details}
|
||||||
onChange={(value) => setDetails(value)}
|
onChange={(value) => setDetails(value)}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
queryFindGalleriesForSelect,
|
queryFindGalleriesForSelect,
|
||||||
queryFindGalleriesByIDForSelect,
|
queryFindGalleriesByIDForSelect,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { defaultMaxOptionsShown } from "src/core/config";
|
import { defaultMaxOptionsShown } from "src/core/config";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
|
@ -70,7 +70,7 @@ const gallerySelectSort = PatchFunction(
|
||||||
const _GallerySelect: React.FC<
|
const _GallerySelect: React.FC<
|
||||||
IFilterProps & IFilterValueProps<Gallery> & ExtraGalleryProps
|
IFilterProps & IFilterValueProps<Gallery> & ExtraGalleryProps
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const maxOptionsShown =
|
const maxOptionsShown =
|
||||||
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { FormattedMessage } from "react-intl";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
import { faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
|
import { faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { RelatedGroupPopoverButton } from "./RelatedGroupPopover";
|
import { RelatedGroupPopoverButton } from "./RelatedGroupPopover";
|
||||||
import { SweatDrops } from "../Shared/SweatDrops";
|
import { OCounterButton } from "../Shared/CountButton";
|
||||||
|
|
||||||
const Description: React.FC<{
|
const Description: React.FC<{
|
||||||
sceneNumber?: number;
|
sceneNumber?: number;
|
||||||
|
|
@ -111,16 +111,7 @@ export const GroupCard: React.FC<IProps> = ({
|
||||||
function maybeRenderOCounter() {
|
function maybeRenderOCounter() {
|
||||||
if (!group.o_counter) return;
|
if (!group.o_counter) return;
|
||||||
|
|
||||||
return (
|
return <OCounterButton value={group.o_counter} />;
|
||||||
<div className="o-counter">
|
|
||||||
<Button className="minimal">
|
|
||||||
<span className="fa-icon">
|
|
||||||
<SweatDrops />
|
|
||||||
</span>
|
|
||||||
<span>{group.o_counter}</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderPopoverButtonGroup() {
|
function maybeRenderPopoverButtonGroup() {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import {
|
||||||
import { GroupEditPanel } from "./GroupEditPanel";
|
import { GroupEditPanel } from "./GroupEditPanel";
|
||||||
import { faRefresh, faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
import { faRefresh, faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { DetailImage } from "src/components/Shared/DetailImage";
|
import { DetailImage } from "src/components/Shared/DetailImage";
|
||||||
import { useRatingKeybinds } from "src/hooks/keybinds";
|
import { useRatingKeybinds } from "src/hooks/keybinds";
|
||||||
import { useLoadStickyHeader } from "src/hooks/detailsPanel";
|
import { useLoadStickyHeader } from "src/hooks/detailsPanel";
|
||||||
|
|
@ -146,7 +146,7 @@ const GroupPage: React.FC<IProps> = ({ group, tabKey }) => {
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
|
|
||||||
// Configuration settings
|
// Configuration settings
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const uiConfig = configuration?.ui;
|
const uiConfig = configuration?.ui;
|
||||||
const enableBackgroundImage = uiConfig?.enableMovieBackgroundImage ?? false;
|
const enableBackgroundImage = uiConfig?.enableMovieBackgroundImage ?? false;
|
||||||
const compactExpandedDetails = uiConfig?.compactExpandedDetails ?? false;
|
const compactExpandedDetails = uiConfig?.compactExpandedDetails ?? false;
|
||||||
|
|
|
||||||
|
|
@ -149,37 +149,44 @@ export const GroupScrapeDialog: React.FC<IGroupScrapeDialogProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="name"
|
||||||
title={intl.formatMessage({ id: "name" })}
|
title={intl.formatMessage({ id: "name" })}
|
||||||
result={name}
|
result={name}
|
||||||
onChange={(value) => setName(value)}
|
onChange={(value) => setName(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="aliases"
|
||||||
title={intl.formatMessage({ id: "aliases" })}
|
title={intl.formatMessage({ id: "aliases" })}
|
||||||
result={aliases}
|
result={aliases}
|
||||||
onChange={(value) => setAliases(value)}
|
onChange={(value) => setAliases(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="duration"
|
||||||
title={intl.formatMessage({ id: "duration" })}
|
title={intl.formatMessage({ id: "duration" })}
|
||||||
result={duration}
|
result={duration}
|
||||||
onChange={(value) => setDuration(value)}
|
onChange={(value) => setDuration(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="date"
|
||||||
title={intl.formatMessage({ id: "date" })}
|
title={intl.formatMessage({ id: "date" })}
|
||||||
placeholder="YYYY-MM-DD"
|
placeholder="YYYY-MM-DD"
|
||||||
result={date}
|
result={date}
|
||||||
onChange={(value) => setDate(value)}
|
onChange={(value) => setDate(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="director"
|
||||||
title={intl.formatMessage({ id: "director" })}
|
title={intl.formatMessage({ id: "director" })}
|
||||||
result={director}
|
result={director}
|
||||||
onChange={(value) => setDirector(value)}
|
onChange={(value) => setDirector(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedTextAreaRow
|
<ScrapedTextAreaRow
|
||||||
|
field="synopsis"
|
||||||
title={intl.formatMessage({ id: "synopsis" })}
|
title={intl.formatMessage({ id: "synopsis" })}
|
||||||
result={synopsis}
|
result={synopsis}
|
||||||
onChange={(value) => setSynopsis(value)}
|
onChange={(value) => setSynopsis(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStudioRow
|
<ScrapedStudioRow
|
||||||
|
field="studio"
|
||||||
title={intl.formatMessage({ id: "studios" })}
|
title={intl.formatMessage({ id: "studios" })}
|
||||||
result={studio}
|
result={studio}
|
||||||
onChange={(value) => setStudio(value)}
|
onChange={(value) => setStudio(value)}
|
||||||
|
|
@ -187,18 +194,21 @@ export const GroupScrapeDialog: React.FC<IGroupScrapeDialogProps> = ({
|
||||||
onCreateNew={createNewStudio}
|
onCreateNew={createNewStudio}
|
||||||
/>
|
/>
|
||||||
<ScrapedStringListRow
|
<ScrapedStringListRow
|
||||||
|
field="urls"
|
||||||
title={intl.formatMessage({ id: "urls" })}
|
title={intl.formatMessage({ id: "urls" })}
|
||||||
result={urls}
|
result={urls}
|
||||||
onChange={(value) => setURLs(value)}
|
onChange={(value) => setURLs(value)}
|
||||||
/>
|
/>
|
||||||
{scrapedTagsRow}
|
{scrapedTagsRow}
|
||||||
<ScrapedImageRow
|
<ScrapedImageRow
|
||||||
|
field="front_image"
|
||||||
title="Front Image"
|
title="Front Image"
|
||||||
className="group-image"
|
className="group-image"
|
||||||
result={frontImage}
|
result={frontImage}
|
||||||
onChange={(value) => setFrontImage(value)}
|
onChange={(value) => setFrontImage(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedImageRow
|
<ScrapedImageRow
|
||||||
|
field="back_image"
|
||||||
title="Back Image"
|
title="Back Image"
|
||||||
className="group-image"
|
className="group-image"
|
||||||
result={backImage}
|
result={backImage}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
queryFindGroupsByIDForSelect,
|
queryFindGroupsByIDForSelect,
|
||||||
useGroupCreate,
|
useGroupCreate,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { defaultMaxOptionsShown } from "src/core/config";
|
import { defaultMaxOptionsShown } from "src/core/config";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
|
@ -66,7 +66,7 @@ export const GroupSelect: React.FC<
|
||||||
> = PatchComponent("GroupSelect", (props) => {
|
> = PatchComponent("GroupSelect", (props) => {
|
||||||
const [createGroup] = useGroupCreate();
|
const [createGroup] = useGroupCreate();
|
||||||
|
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const maxOptionsShown =
|
const maxOptionsShown =
|
||||||
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { useImagesDestroy } from "src/core/StashService";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { ModalComponent } from "src/components/Shared/Modal";
|
import { ModalComponent } from "src/components/Shared/Modal";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ export const DeleteImagesDialog: React.FC<IDeleteImageDialogProps> = (
|
||||||
{ count: props.selected.length, singularEntity, pluralEntity }
|
{ count: props.selected.length, singularEntity, pluralEntity }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
|
|
||||||
const [deleteFile, setDeleteFile] = useState<boolean>(
|
const [deleteFile, setDeleteFile] = useState<boolean>(
|
||||||
config?.defaults.deleteFile ?? false
|
config?.defaults.deleteFile ?? false
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import * as GQL from "src/core/generated-graphql";
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import { GalleryLink, TagLink } from "src/components/Shared/TagLink";
|
import { GalleryLink, TagLink } from "src/components/Shared/TagLink";
|
||||||
import { HoverPopover } from "src/components/Shared/HoverPopover";
|
import { HoverPopover } from "src/components/Shared/HoverPopover";
|
||||||
import { SweatDrops } from "src/components/Shared/SweatDrops";
|
|
||||||
import { PerformerPopoverButton } from "src/components/Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "src/components/Shared/PerformerPopoverButton";
|
||||||
import { GridCard } from "src/components/Shared/GridCard/GridCard";
|
import { GridCard } from "src/components/Shared/GridCard/GridCard";
|
||||||
import { RatingBanner } from "src/components/Shared/RatingBanner";
|
import { RatingBanner } from "src/components/Shared/RatingBanner";
|
||||||
|
|
@ -18,6 +17,7 @@ import {
|
||||||
import { imageTitle } from "src/core/files";
|
import { imageTitle } from "src/core/files";
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
||||||
|
import { OCounterButton } from "../Shared/CountButton";
|
||||||
|
|
||||||
interface IImageCardProps {
|
interface IImageCardProps {
|
||||||
image: GQL.SlimImageDataFragment;
|
image: GQL.SlimImageDataFragment;
|
||||||
|
|
@ -74,16 +74,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
||||||
|
|
||||||
function maybeRenderOCounter() {
|
function maybeRenderOCounter() {
|
||||||
if (props.image.o_counter) {
|
if (props.image.o_counter) {
|
||||||
return (
|
return <OCounterButton value={props.image.o_counter} />;
|
||||||
<div className="o-count">
|
|
||||||
<Button className="minimal">
|
|
||||||
<span className="fa-icon">
|
|
||||||
<SweatDrops />
|
|
||||||
</span>
|
|
||||||
<span>{props.image.o_counter}</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Tab, Nav, Dropdown } from "react-bootstrap";
|
import { Tab, Nav, Dropdown } from "react-bootstrap";
|
||||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { FormattedDate, FormattedMessage, useIntl } from "react-intl";
|
import { FormattedDate, FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useHistory, Link, RouteComponentProps } from "react-router-dom";
|
import { useHistory, Link, RouteComponentProps } from "react-router-dom";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
|
|
@ -29,7 +29,7 @@ import { imagePath, imageTitle } from "src/core/files";
|
||||||
import { isVideo } from "src/utils/visualFile";
|
import { isVideo } from "src/utils/visualFile";
|
||||||
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
||||||
import { useRatingKeybinds } from "src/hooks/keybinds";
|
import { useRatingKeybinds } from "src/hooks/keybinds";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
|
|
@ -48,7 +48,7 @@ const ImagePage: React.FC<IProps> = ({ image }) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
const [incrementO] = useImageIncrementO(image.id);
|
const [incrementO] = useImageIncrementO(image.id);
|
||||||
const [decrementO] = useImageDecrementO(image.id);
|
const [decrementO] = useImageDecrementO(image.id);
|
||||||
|
|
|
||||||
|
|
@ -163,32 +163,38 @@ export const ImageScrapeDialog: React.FC<IImageScrapeDialogProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="title"
|
||||||
title={intl.formatMessage({ id: "title" })}
|
title={intl.formatMessage({ id: "title" })}
|
||||||
result={title}
|
result={title}
|
||||||
onChange={(value) => setTitle(value)}
|
onChange={(value) => setTitle(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="code"
|
||||||
title={intl.formatMessage({ id: "scene_code" })}
|
title={intl.formatMessage({ id: "scene_code" })}
|
||||||
result={code}
|
result={code}
|
||||||
onChange={(value) => setCode(value)}
|
onChange={(value) => setCode(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStringListRow
|
<ScrapedStringListRow
|
||||||
|
field="urls"
|
||||||
title={intl.formatMessage({ id: "urls" })}
|
title={intl.formatMessage({ id: "urls" })}
|
||||||
result={urls}
|
result={urls}
|
||||||
onChange={(value) => setURLs(value)}
|
onChange={(value) => setURLs(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="date"
|
||||||
title={intl.formatMessage({ id: "date" })}
|
title={intl.formatMessage({ id: "date" })}
|
||||||
placeholder="YYYY-MM-DD"
|
placeholder="YYYY-MM-DD"
|
||||||
result={date}
|
result={date}
|
||||||
onChange={(value) => setDate(value)}
|
onChange={(value) => setDate(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="photographer"
|
||||||
title={intl.formatMessage({ id: "photographer" })}
|
title={intl.formatMessage({ id: "photographer" })}
|
||||||
result={photographer}
|
result={photographer}
|
||||||
onChange={(value) => setPhotographer(value)}
|
onChange={(value) => setPhotographer(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStudioRow
|
<ScrapedStudioRow
|
||||||
|
field="studio"
|
||||||
title={intl.formatMessage({ id: "studios" })}
|
title={intl.formatMessage({ id: "studios" })}
|
||||||
result={studio}
|
result={studio}
|
||||||
onChange={(value) => setStudio(value)}
|
onChange={(value) => setStudio(value)}
|
||||||
|
|
@ -196,6 +202,7 @@ export const ImageScrapeDialog: React.FC<IImageScrapeDialogProps> = ({
|
||||||
onCreateNew={createNewStudio}
|
onCreateNew={createNewStudio}
|
||||||
/>
|
/>
|
||||||
<ScrapedPerformersRow
|
<ScrapedPerformersRow
|
||||||
|
field="performers"
|
||||||
title={intl.formatMessage({ id: "performers" })}
|
title={intl.formatMessage({ id: "performers" })}
|
||||||
result={performers}
|
result={performers}
|
||||||
onChange={(value) => setPerformers(value)}
|
onChange={(value) => setPerformers(value)}
|
||||||
|
|
@ -204,6 +211,7 @@ export const ImageScrapeDialog: React.FC<IImageScrapeDialogProps> = ({
|
||||||
/>
|
/>
|
||||||
{scrapedTagsRow}
|
{scrapedTagsRow}
|
||||||
<ScrapedTextAreaRow
|
<ScrapedTextAreaRow
|
||||||
|
field="details"
|
||||||
title={intl.formatMessage({ id: "details" })}
|
title={intl.formatMessage({ id: "details" })}
|
||||||
result={details}
|
result={details}
|
||||||
onChange={(value) => setDetails(value)}
|
onChange={(value) => setDetails(value)}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
import React, {
|
import React, { useCallback, useState, useMemo, MouseEvent } from "react";
|
||||||
useCallback,
|
|
||||||
useState,
|
|
||||||
useMemo,
|
|
||||||
MouseEvent,
|
|
||||||
useContext,
|
|
||||||
} from "react";
|
|
||||||
import { FormattedNumber, useIntl } from "react-intl";
|
import { FormattedNumber, useIntl } from "react-intl";
|
||||||
import cloneDeep from "lodash-es/cloneDeep";
|
import cloneDeep from "lodash-es/cloneDeep";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
@ -23,7 +17,7 @@ import "flexbin/flexbin.css";
|
||||||
import Gallery, { RenderImageProps } from "react-photo-gallery";
|
import Gallery, { RenderImageProps } from "react-photo-gallery";
|
||||||
import { ExportDialog } from "../Shared/ExportDialog";
|
import { ExportDialog } from "../Shared/ExportDialog";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { ImageGridCard } from "./ImageGridCard";
|
import { ImageGridCard } from "./ImageGridCard";
|
||||||
import { View } from "../List/views";
|
import { View } from "../List/views";
|
||||||
import { IItemListOperation } from "../List/FilteredListToolbar";
|
import { IItemListOperation } from "../List/FilteredListToolbar";
|
||||||
|
|
@ -51,7 +45,7 @@ const ImageWall: React.FC<IImageWallProps> = ({
|
||||||
zoomIndex,
|
zoomIndex,
|
||||||
handleImageOpen,
|
handleImageOpen,
|
||||||
}) => {
|
}) => {
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const uiConfig = configuration?.ui;
|
const uiConfig = configuration?.ui;
|
||||||
|
|
||||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import cloneDeep from "lodash-es/cloneDeep";
|
import cloneDeep from "lodash-es/cloneDeep";
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
|
|
@ -14,7 +13,7 @@ import {
|
||||||
CriterionOption,
|
CriterionOption,
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { getFilterOptions } from "src/models/list-filter/factory";
|
import { getFilterOptions } from "src/models/list-filter/factory";
|
||||||
import { FilterTags } from "./FilterTags";
|
import { FilterTags } from "./FilterTags";
|
||||||
|
|
@ -65,6 +64,9 @@ const CriterionOptionList: React.FC<ICriterionList> = ({
|
||||||
onTogglePin,
|
onTogglePin,
|
||||||
externallySelected = false,
|
externallySelected = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { configuration } = useConfigurationContext();
|
||||||
|
const { sfwContentMode } = configuration.interface;
|
||||||
|
|
||||||
const prevCriterion = usePrevious(currentCriterion);
|
const prevCriterion = usePrevious(currentCriterion);
|
||||||
|
|
||||||
const scrolled = useRef(false);
|
const scrolled = useRef(false);
|
||||||
|
|
@ -148,7 +150,9 @@ const CriterionOptionList: React.FC<ICriterionList> = ({
|
||||||
className="collapse-icon fa-fw"
|
className="collapse-icon fa-fw"
|
||||||
icon={type === c.type ? faChevronDown : faChevronRight}
|
icon={type === c.type ? faChevronDown : faChevronRight}
|
||||||
/>
|
/>
|
||||||
<FormattedMessage id={c.messageID} />
|
<FormattedMessage
|
||||||
|
id={!sfwContentMode ? c.messageID : c.sfwMessageID ?? c.messageID}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
{criteria.some((cc) => c.type === cc) && (
|
{criteria.some((cc) => c.type === cc) && (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -233,7 +237,8 @@ export const EditFilterDialog: React.FC<IEditFilterProps> = ({
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
const { sfwContentMode } = configuration.interface;
|
||||||
|
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
const [currentFilter, setCurrentFilter] = useState<ListFilterModel>(
|
const [currentFilter, setCurrentFilter] = useState<ListFilterModel>(
|
||||||
|
|
@ -265,10 +270,16 @@ export const EditFilterDialog: React.FC<IEditFilterProps> = ({
|
||||||
.filter((c) => !c.hidden)
|
.filter((c) => !c.hidden)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
return intl
|
return intl
|
||||||
.formatMessage({ id: a.messageID })
|
.formatMessage({
|
||||||
.localeCompare(intl.formatMessage({ id: b.messageID }));
|
id: !sfwContentMode ? a.messageID : a.sfwMessageID ?? a.messageID,
|
||||||
|
})
|
||||||
|
.localeCompare(
|
||||||
|
intl.formatMessage({
|
||||||
|
id: !sfwContentMode ? b.messageID : b.sfwMessageID ?? b.messageID,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}, [intl, filterOptions.criterionOptions]);
|
}, [intl, sfwContentMode, filterOptions.criterionOptions]);
|
||||||
|
|
||||||
const optionSelected = useCallback(
|
const optionSelected = useCallback(
|
||||||
(option?: CriterionOption) => {
|
(option?: CriterionOption) => {
|
||||||
|
|
@ -302,11 +313,13 @@ export const EditFilterDialog: React.FC<IEditFilterProps> = ({
|
||||||
|
|
||||||
return criterionOptions.filter((c) => {
|
return criterionOptions.filter((c) => {
|
||||||
return intl
|
return intl
|
||||||
.formatMessage({ id: c.messageID })
|
.formatMessage({
|
||||||
|
id: !sfwContentMode ? c.messageID : c.sfwMessageID ?? c.messageID,
|
||||||
|
})
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(trimmedSearch);
|
.includes(trimmedSearch);
|
||||||
});
|
});
|
||||||
}, [intl, searchValue, criterionOptions]);
|
}, [intl, sfwContentMode, searchValue, criterionOptions]);
|
||||||
|
|
||||||
const pinnedFilters = useMemo(
|
const pinnedFilters = useMemo(
|
||||||
() => ui.pinnedFilters?.[filterModeToConfigKey(currentFilter.mode)] ?? [],
|
() => ui.pinnedFilters?.[filterModeToConfigKey(currentFilter.mode)] ?? [],
|
||||||
|
|
@ -517,7 +530,10 @@ export const EditFilterDialog: React.FC<IEditFilterProps> = ({
|
||||||
<Modal
|
<Modal
|
||||||
show={!showSaveDialog && !showLoadDialog}
|
show={!showSaveDialog && !showLoadDialog}
|
||||||
onHide={() => onCancel()}
|
onHide={() => onCancel()}
|
||||||
className="edit-filter-dialog"
|
// need sfw mode class because dialog is outside body
|
||||||
|
className={cx("edit-filter-dialog", {
|
||||||
|
"sfw-content-mode": sfwContentMode,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<Modal.Header>
|
<Modal.Header>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { BsPrefixProps, ReplaceProps } from "react-bootstrap/esm/helpers";
|
||||||
import { CustomFieldsCriterion } from "src/models/list-filter/criteria/custom-fields";
|
import { CustomFieldsCriterion } from "src/models/list-filter/criteria/custom-fields";
|
||||||
import { useDebounce } from "src/hooks/debounce";
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
type TagItemProps = PropsWithChildren<
|
type TagItemProps = PropsWithChildren<
|
||||||
ReplaceProps<"span", BsPrefixProps<"span"> & BadgeProps>
|
ReplaceProps<"span", BsPrefixProps<"span"> & BadgeProps>
|
||||||
|
|
@ -125,6 +126,9 @@ export const FilterTags: React.FC<IFilterTagsProps> = ({
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const { configuration } = useConfigurationContext();
|
||||||
|
const { sfwContentMode } = configuration.interface;
|
||||||
|
|
||||||
const [cutoff, setCutoff] = React.useState<number | undefined>();
|
const [cutoff, setCutoff] = React.useState<number | undefined>();
|
||||||
const elementGap = 10; // Adjust this value based on your CSS gap or margin
|
const elementGap = 10; // Adjust this value based on your CSS gap or margin
|
||||||
const moreTagWidth = 80; // reserve space for the "more" tag
|
const moreTagWidth = 80; // reserve space for the "more" tag
|
||||||
|
|
@ -270,7 +274,7 @@ export const FilterTags: React.FC<IFilterTagsProps> = ({
|
||||||
return (
|
return (
|
||||||
<FilterTag
|
<FilterTag
|
||||||
key={criterion.getId()}
|
key={criterion.getId()}
|
||||||
label={criterion.getLabel(intl)}
|
label={criterion.getLabel(intl, sfwContentMode)}
|
||||||
onClick={() => onClickCriterionTag(criterion)}
|
onClick={() => onClickCriterionTag(criterion)}
|
||||||
onRemove={($event) => onRemoveCriterionTag(criterion, $event)}
|
onRemove={($event) => onRemoveCriterionTag(criterion, $event)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React from "react";
|
||||||
import { Form } from "react-bootstrap";
|
import { Form } from "react-bootstrap";
|
||||||
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import {
|
import {
|
||||||
ModifierCriterion,
|
ModifierCriterion,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
|
|
@ -17,7 +17,7 @@ export const PathFilter: React.FC<IInputFilterProps> = ({
|
||||||
criterion,
|
criterion,
|
||||||
onValueChanged,
|
onValueChanged,
|
||||||
}) => {
|
}) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const libraryPaths = configuration?.general.stashes.map((s) => s.path);
|
const libraryPaths = configuration?.general.stashes.map((s) => s.path);
|
||||||
|
|
||||||
// don't show folder select for regex
|
// don't show folder select for regex
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
defaultRatingStarPrecision,
|
defaultRatingStarPrecision,
|
||||||
defaultRatingSystemOptions,
|
defaultRatingSystemOptions,
|
||||||
} from "src/utils/rating";
|
} from "src/utils/rating";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { RatingCriterion } from "src/models/list-filter/criteria/rating";
|
import { RatingCriterion } from "src/models/list-filter/criteria/rating";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { Option, SidebarListFilter } from "./SidebarListFilter";
|
import { Option, SidebarListFilter } from "./SidebarListFilter";
|
||||||
|
|
@ -117,7 +117,7 @@ export const SidebarRatingFilter: React.FC<ISidebarFilter> = ({
|
||||||
[noneLabel]
|
[noneLabel]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
const ratingSystemOptions =
|
const ratingSystemOptions =
|
||||||
config?.ui.ratingSystemOptions ?? defaultRatingSystemOptions;
|
config?.ui.ratingSystemOptions ?? defaultRatingSystemOptions;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React, {
|
import React, {
|
||||||
PropsWithChildren,
|
PropsWithChildren,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
|
|
@ -43,7 +42,7 @@ import {
|
||||||
IItemListOperation,
|
IItemListOperation,
|
||||||
} from "./FilteredListToolbar";
|
} from "./FilteredListToolbar";
|
||||||
import { PagedList } from "./PagedList";
|
import { PagedList } from "./PagedList";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
interface IFilteredItemList<T extends QueryResult, E extends IHasID = IHasID> {
|
interface IFilteredItemList<T extends QueryResult, E extends IHasID = IHasID> {
|
||||||
filterStateProps: IFilterStateHook;
|
filterStateProps: IFilterStateHook;
|
||||||
|
|
@ -55,7 +54,7 @@ export function useFilteredItemList<
|
||||||
T extends QueryResult,
|
T extends QueryResult,
|
||||||
E extends IHasID = IHasID
|
E extends IHasID = IHasID
|
||||||
>(props: IFilteredItemList<T, E>) {
|
>(props: IFilteredItemList<T, E>) {
|
||||||
const { configuration: config } = useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
|
|
||||||
// States
|
// States
|
||||||
const filterState = useFilterState({
|
const filterState = useFilterState({
|
||||||
|
|
@ -393,7 +392,7 @@ export const ItemListContext = <T extends QueryResult, E extends IHasID>(
|
||||||
children,
|
children,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { configuration: config } = useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
|
|
||||||
const emptyFilter = useMemo(
|
const emptyFilter = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -409,12 +408,7 @@ export const ItemListContext = <T extends QueryResult, E extends IHasID>(
|
||||||
new ListFilterModel(filterMode, config, { defaultSortBy: defaultSort })
|
new ListFilterModel(filterMode, config, { defaultSortBy: defaultSort })
|
||||||
);
|
);
|
||||||
|
|
||||||
const { defaultFilter, loading: defaultFilterLoading } = useDefaultFilter(
|
const { defaultFilter } = useDefaultFilter(emptyFilter, view);
|
||||||
emptyFilter,
|
|
||||||
view
|
|
||||||
);
|
|
||||||
|
|
||||||
if (defaultFilterLoading) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FilterContext filter={filter} setFilter={setFilterState}>
|
<FilterContext filter={filter} setFilter={setFilterState}>
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import { View } from "./views";
|
||||||
import { ClearableInput } from "../Shared/ClearableInput";
|
import { ClearableInput } from "../Shared/ClearableInput";
|
||||||
import { useStopWheelScroll } from "src/utils/form";
|
import { useStopWheelScroll } from "src/utils/form";
|
||||||
import { ISortByOption } from "src/models/list-filter/filter-options";
|
import { ISortByOption } from "src/models/list-filter/filter-options";
|
||||||
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
export function useDebouncedSearchInput(
|
export function useDebouncedSearchInput(
|
||||||
filter: ListFilterModel,
|
filter: ListFilterModel,
|
||||||
|
|
@ -249,14 +250,24 @@ export const SortBySelect: React.FC<{
|
||||||
onReshuffleRandomSort,
|
onReshuffleRandomSort,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { configuration } = useConfigurationContext();
|
||||||
|
const { sfwContentMode } = configuration.interface;
|
||||||
|
|
||||||
const currentSortBy = options.find((o) => o.value === sortBy);
|
const currentSortBy = options.find((o) => o.value === sortBy);
|
||||||
|
const currentSortByMessageID = currentSortBy
|
||||||
|
? !sfwContentMode
|
||||||
|
? currentSortBy.messageID
|
||||||
|
: currentSortBy.sfwMessageID ?? currentSortBy.messageID
|
||||||
|
: "";
|
||||||
|
|
||||||
function renderSortByOptions() {
|
function renderSortByOptions() {
|
||||||
return options
|
return options
|
||||||
.map((o) => {
|
.map((o) => {
|
||||||
|
const messageID = !sfwContentMode
|
||||||
|
? o.messageID
|
||||||
|
: o.sfwMessageID ?? o.messageID;
|
||||||
return {
|
return {
|
||||||
message: intl.formatMessage({ id: o.messageID }),
|
message: intl.formatMessage({ id: messageID }),
|
||||||
value: o.value,
|
value: o.value,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
@ -267,6 +278,7 @@ export const SortBySelect: React.FC<{
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className="bg-secondary text-white"
|
className="bg-secondary text-white"
|
||||||
eventKey={option.value}
|
eventKey={option.value}
|
||||||
|
data-value={option.value}
|
||||||
>
|
>
|
||||||
{option.message}
|
{option.message}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
@ -274,11 +286,11 @@ export const SortBySelect: React.FC<{
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown as={ButtonGroup} className={className}>
|
<Dropdown as={ButtonGroup} className={`${className ?? ""} sort-by-select`}>
|
||||||
<InputGroup.Prepend>
|
<InputGroup.Prepend>
|
||||||
<Dropdown.Toggle variant="secondary">
|
<Dropdown.Toggle variant="secondary">
|
||||||
{currentSortBy
|
{currentSortBy
|
||||||
? intl.formatMessage({ id: currentSortBy.messageID })
|
? intl.formatMessage({ id: currentSortByMessageID })
|
||||||
: ""}
|
: ""}
|
||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
</InputGroup.Prepend>
|
</InputGroup.Prepend>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
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 { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
import { isEqual, isFunction } from "lodash-es";
|
import { isEqual, isFunction } from "lodash-es";
|
||||||
import { QueryResult } from "@apollo/client";
|
import { QueryResult } from "@apollo/client";
|
||||||
import { IHasID } from "src/utils/data";
|
import { IHasID } from "src/utils/data";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { View } from "./views";
|
import { View } from "./views";
|
||||||
import { usePrevious } from "src/hooks/state";
|
import { usePrevious } from "src/hooks/state";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
|
@ -94,7 +94,7 @@ export function useFilterURL(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDefaultFilter(emptyFilter: ListFilterModel, view?: View) {
|
export function useDefaultFilter(emptyFilter: ListFilterModel, view?: View) {
|
||||||
const { configuration: config, loading } = useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
|
|
||||||
const defaultFilter = useMemo(() => {
|
const defaultFilter = useMemo(() => {
|
||||||
if (view && config?.ui.defaultFilters?.[view]) {
|
if (view && config?.ui.defaultFilters?.[view]) {
|
||||||
|
|
@ -114,9 +114,9 @@ export function useDefaultFilter(emptyFilter: ListFilterModel, view?: View) {
|
||||||
}
|
}
|
||||||
}, [view, config?.ui.defaultFilters, emptyFilter]);
|
}, [view, config?.ui.defaultFilters, emptyFilter]);
|
||||||
|
|
||||||
const retFilter = loading ? undefined : defaultFilter ?? emptyFilter;
|
const retFilter = defaultFilter ?? emptyFilter;
|
||||||
|
|
||||||
return { defaultFilter: retFilter, loading };
|
return { defaultFilter: retFilter };
|
||||||
}
|
}
|
||||||
|
|
||||||
function useEmptyFilter(props: {
|
function useEmptyFilter(props: {
|
||||||
|
|
@ -158,14 +158,14 @@ export function useFilterState(
|
||||||
|
|
||||||
const emptyFilter = useEmptyFilter({ filterMode, defaultSort, config });
|
const emptyFilter = useEmptyFilter({ filterMode, defaultSort, config });
|
||||||
|
|
||||||
const { defaultFilter, loading } = useDefaultFilter(emptyFilter, view);
|
const { defaultFilter } = useDefaultFilter(emptyFilter, view);
|
||||||
|
|
||||||
const { setFilter } = useFilterURL(filter, setFilterState, {
|
const { setFilter } = useFilterURL(filter, setFilterState, {
|
||||||
defaultFilter,
|
defaultFilter,
|
||||||
active: useURL,
|
active: useURL,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { loading, filter, setFilter };
|
return { filter, setFilter };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFilterOperations(props: {
|
export function useFilterOperations(props: {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
MessageDescriptor,
|
MessageDescriptor,
|
||||||
useIntl,
|
useIntl,
|
||||||
} from "react-intl";
|
} from "react-intl";
|
||||||
import { Nav, Navbar, Button, Fade } from "react-bootstrap";
|
import { Nav, Navbar, Button } from "react-bootstrap";
|
||||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||||
import { LinkContainer } from "react-router-bootstrap";
|
import { LinkContainer } from "react-router-bootstrap";
|
||||||
import { Link, NavLink, useLocation, useHistory } from "react-router-dom";
|
import { Link, NavLink, useLocation, useHistory } from "react-router-dom";
|
||||||
|
|
@ -19,7 +19,7 @@ import Mousetrap from "mousetrap";
|
||||||
|
|
||||||
import SessionUtils from "src/utils/session";
|
import SessionUtils from "src/utils/session";
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { ManualStateContext } from "./Help/context";
|
import { ManualStateContext } from "./Help/context";
|
||||||
import { SettingsButton } from "./SettingsButton";
|
import { SettingsButton } from "./SettingsButton";
|
||||||
import {
|
import {
|
||||||
|
|
@ -181,7 +181,7 @@ const MainNavbarUtilityItems = PatchComponent(
|
||||||
export const MainNavbar: React.FC = () => {
|
export const MainNavbar: React.FC = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { configuration, loading } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const { openManual } = React.useContext(ManualStateContext);
|
const { openManual } = React.useContext(ManualStateContext);
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
@ -359,35 +359,31 @@ export const MainNavbar: React.FC = () => {
|
||||||
ref={navbarRef}
|
ref={navbarRef}
|
||||||
>
|
>
|
||||||
<Navbar.Collapse className="bg-dark order-sm-1">
|
<Navbar.Collapse className="bg-dark order-sm-1">
|
||||||
<Fade in={!loading}>
|
<MainNavbarMenuItems>
|
||||||
<>
|
{menuItems.map(({ href, icon, message }) => (
|
||||||
<MainNavbarMenuItems>
|
<Nav.Link
|
||||||
{menuItems.map(({ href, icon, message }) => (
|
eventKey={href}
|
||||||
<Nav.Link
|
as="div"
|
||||||
eventKey={href}
|
key={href}
|
||||||
as="div"
|
className="col-4 col-sm-3 col-md-2 col-lg-auto"
|
||||||
key={href}
|
>
|
||||||
className="col-4 col-sm-3 col-md-2 col-lg-auto"
|
<LinkContainer activeClassName="active" exact to={href}>
|
||||||
>
|
<Button className="minimal p-4 p-xl-2 d-flex d-xl-inline-block flex-column justify-content-between align-items-center">
|
||||||
<LinkContainer activeClassName="active" exact to={href}>
|
<Icon
|
||||||
<Button className="minimal p-4 p-xl-2 d-flex d-xl-inline-block flex-column justify-content-between align-items-center">
|
{...{ icon }}
|
||||||
<Icon
|
className="nav-menu-icon d-block d-xl-inline mb-2 mb-xl-0"
|
||||||
{...{ icon }}
|
/>
|
||||||
className="nav-menu-icon d-block d-xl-inline mb-2 mb-xl-0"
|
<span>{intl.formatMessage(message)}</span>
|
||||||
/>
|
</Button>
|
||||||
<span>{intl.formatMessage(message)}</span>
|
</LinkContainer>
|
||||||
</Button>
|
</Nav.Link>
|
||||||
</LinkContainer>
|
))}
|
||||||
</Nav.Link>
|
</MainNavbarMenuItems>
|
||||||
))}
|
<Nav>
|
||||||
</MainNavbarMenuItems>
|
<MainNavbarUtilityItems>
|
||||||
<Nav>
|
{renderUtilityButtons()}
|
||||||
<MainNavbarUtilityItems>
|
</MainNavbarUtilityItems>
|
||||||
{renderUtilityButtons()}
|
</Nav>
|
||||||
</MainNavbarUtilityItems>
|
|
||||||
</Nav>
|
|
||||||
</>
|
|
||||||
</Fade>
|
|
||||||
</Navbar.Collapse>
|
</Navbar.Collapse>
|
||||||
|
|
||||||
<Navbar.Brand as="div" onClick={handleDismiss}>
|
<Navbar.Brand as="div" onClick={handleDismiss}>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ import { BulkUpdateTextInput } from "../Shared/BulkUpdateTextInput";
|
||||||
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
import * as FormUtils from "src/utils/form";
|
import * as FormUtils from "src/utils/form";
|
||||||
import { CountrySelect } from "../Shared/CountrySelect";
|
import { CountrySelect } from "../Shared/CountrySelect";
|
||||||
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
import cx from "classnames";
|
||||||
|
|
||||||
interface IListOperationProps {
|
interface IListOperationProps {
|
||||||
selected: GQL.SlimPerformerDataFragment[];
|
selected: GQL.SlimPerformerDataFragment[];
|
||||||
|
|
@ -61,6 +63,10 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
||||||
) => {
|
) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
|
|
||||||
|
const { configuration } = useConfigurationContext();
|
||||||
|
const { sfwContentMode } = configuration.interface;
|
||||||
|
|
||||||
const [tagIds, setTagIds] = useState<GQL.BulkUpdateIds>({
|
const [tagIds, setTagIds] = useState<GQL.BulkUpdateIds>({
|
||||||
mode: GQL.BulkUpdateIdMode.Add,
|
mode: GQL.BulkUpdateIdMode.Add,
|
||||||
});
|
});
|
||||||
|
|
@ -204,7 +210,7 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
||||||
setter: (newValue: string | undefined) => void
|
setter: (newValue: string | undefined) => void
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<Form.Group controlId={name}>
|
<Form.Group controlId={name} data-field={name}>
|
||||||
<Form.Label>
|
<Form.Label>
|
||||||
<FormattedMessage id={name} />
|
<FormattedMessage id={name} />
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
|
|
@ -218,9 +224,13 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
|
// sfw class needs to be set because it is outside body
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalComponent
|
<ModalComponent
|
||||||
dialogClassName="edit-performers-dialog"
|
dialogClassName={cx("edit-performers-dialog", {
|
||||||
|
"sfw-content-mode": sfwContentMode,
|
||||||
|
})}
|
||||||
show
|
show
|
||||||
icon={faPencilAlt}
|
icon={faPencilAlt}
|
||||||
header={intl.formatMessage(
|
header={intl.formatMessage(
|
||||||
|
|
@ -238,7 +248,7 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
||||||
}}
|
}}
|
||||||
isRunning={isUpdating}
|
isRunning={isUpdating}
|
||||||
>
|
>
|
||||||
<Form.Group controlId="rating" as={Row}>
|
<Form.Group controlId="rating" as={Row} data-field={name}>
|
||||||
{FormUtils.renderLabel({
|
{FormUtils.renderLabel({
|
||||||
title: intl.formatMessage({ id: "rating" }),
|
title: intl.formatMessage({ id: "rating" }),
|
||||||
})}
|
})}
|
||||||
|
|
@ -322,7 +332,7 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
||||||
setPenisLength(v)
|
setPenisLength(v)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Form.Group>
|
<Form.Group data-field="circumcised">
|
||||||
<Form.Label>
|
<Form.Label>
|
||||||
<FormattedMessage id="circumcised" />
|
<FormattedMessage id="circumcised" />
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import NavUtils from "src/utils/navigation";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { GridCard } from "../Shared/GridCard/GridCard";
|
import { GridCard } from "../Shared/GridCard/GridCard";
|
||||||
import { CountryFlag } from "../Shared/CountryFlag";
|
import { CountryFlag } from "../Shared/CountryFlag";
|
||||||
import { SweatDrops } from "../Shared/SweatDrops";
|
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { TagLink } from "../Shared/TagLink";
|
import { TagLink } from "../Shared/TagLink";
|
||||||
|
|
@ -25,7 +24,8 @@ import { ILabeledId } from "src/models/list-filter/types";
|
||||||
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent } from "src/patch";
|
||||||
import { ExternalLinksButton } from "../Shared/ExternalLinksButton";
|
import { ExternalLinksButton } from "../Shared/ExternalLinksButton";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
import { OCounterButton } from "../Shared/CountButton";
|
||||||
|
|
||||||
export interface IPerformerCardExtraCriteria {
|
export interface IPerformerCardExtraCriteria {
|
||||||
scenes?: ModifierCriterion<CriterionValue>[];
|
scenes?: ModifierCriterion<CriterionValue>[];
|
||||||
|
|
@ -103,16 +103,7 @@ const PerformerCardPopovers: React.FC<IPerformerCardProps> = PatchComponent(
|
||||||
function maybeRenderOCounter() {
|
function maybeRenderOCounter() {
|
||||||
if (!performer.o_counter) return;
|
if (!performer.o_counter) return;
|
||||||
|
|
||||||
return (
|
return <OCounterButton value={performer.o_counter} />;
|
||||||
<div className="o-counter">
|
|
||||||
<Button className="minimal">
|
|
||||||
<span className="fa-icon">
|
|
||||||
<SweatDrops />
|
|
||||||
</span>
|
|
||||||
<span>{performer.o_counter}</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderTagPopoverButton() {
|
function maybeRenderTagPopoverButton() {
|
||||||
|
|
@ -179,7 +170,7 @@ const PerformerCardPopovers: React.FC<IPerformerCardProps> = PatchComponent(
|
||||||
const PerformerCardOverlays: React.FC<IPerformerCardProps> = PatchComponent(
|
const PerformerCardOverlays: React.FC<IPerformerCardProps> = PatchComponent(
|
||||||
"PerformerCard.Overlays",
|
"PerformerCard.Overlays",
|
||||||
({ performer }) => {
|
({ performer }) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const uiConfig = configuration?.ui;
|
const uiConfig = configuration?.ui;
|
||||||
const [updatePerformer] = usePerformerUpdate();
|
const [updatePerformer] = usePerformerUpdate();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { DetailsEditNavbar } from "src/components/Shared/DetailsEditNavbar";
|
||||||
import { ErrorMessage } from "src/components/Shared/ErrorMessage";
|
import { ErrorMessage } from "src/components/Shared/ErrorMessage";
|
||||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
import {
|
import {
|
||||||
CompressedPerformerDetailsPanel,
|
CompressedPerformerDetailsPanel,
|
||||||
|
|
@ -42,13 +42,13 @@ import {
|
||||||
import { DetailTitle } from "src/components/Shared/DetailsPage/DetailTitle";
|
import { DetailTitle } from "src/components/Shared/DetailsPage/DetailTitle";
|
||||||
import { ExpandCollapseButton } from "src/components/Shared/CollapseButton";
|
import { ExpandCollapseButton } from "src/components/Shared/CollapseButton";
|
||||||
import { FavoriteIcon } from "src/components/Shared/FavoriteIcon";
|
import { FavoriteIcon } from "src/components/Shared/FavoriteIcon";
|
||||||
import { SweatDrops } from "src/components/Shared/SweatDrops";
|
|
||||||
import { AliasList } from "src/components/Shared/DetailsPage/AliasList";
|
import { AliasList } from "src/components/Shared/DetailsPage/AliasList";
|
||||||
import { HeaderImage } from "src/components/Shared/DetailsPage/HeaderImage";
|
import { HeaderImage } from "src/components/Shared/DetailsPage/HeaderImage";
|
||||||
import { LightboxLink } from "src/hooks/Lightbox/LightboxLink";
|
import { LightboxLink } from "src/hooks/Lightbox/LightboxLink";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent } from "src/patch";
|
||||||
import { ILightboxImage } from "src/hooks/Lightbox/types";
|
import { ILightboxImage } from "src/hooks/Lightbox/types";
|
||||||
import { goBackOrReplace } from "src/utils/history";
|
import { goBackOrReplace } from "src/utils/history";
|
||||||
|
import { OCounterButton } from "src/components/Shared/CountButton";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
|
|
@ -240,7 +240,7 @@ const PerformerPage: React.FC<IProps> = PatchComponent(
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
// Configuration settings
|
// Configuration settings
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const uiConfig = configuration?.ui;
|
const uiConfig = configuration?.ui;
|
||||||
const abbreviateCounter = uiConfig?.abbreviateCounters ?? false;
|
const abbreviateCounter = uiConfig?.abbreviateCounters ?? false;
|
||||||
const enableBackgroundImage =
|
const enableBackgroundImage =
|
||||||
|
|
@ -432,12 +432,7 @@ const PerformerPage: React.FC<IProps> = PatchComponent(
|
||||||
withoutContext
|
withoutContext
|
||||||
/>
|
/>
|
||||||
{!!performer.o_counter && (
|
{!!performer.o_counter && (
|
||||||
<span className="o-counter">
|
<OCounterButton value={performer.o_counter} />
|
||||||
<span className="fa-icon">
|
|
||||||
<SweatDrops />
|
|
||||||
</span>
|
|
||||||
<span>{performer.o_counter}</span>
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isEditing && (
|
{!isEditing && (
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import {
|
||||||
stringCircumMap,
|
stringCircumMap,
|
||||||
stringToCircumcised,
|
stringToCircumcised,
|
||||||
} from "src/utils/circumcised";
|
} from "src/utils/circumcised";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { PerformerScrapeDialog } from "./PerformerScrapeDialog";
|
import { PerformerScrapeDialog } from "./PerformerScrapeDialog";
|
||||||
import PerformerScrapeModal from "./PerformerScrapeModal";
|
import PerformerScrapeModal from "./PerformerScrapeModal";
|
||||||
import PerformerStashBoxModal, { IStashBox } from "./PerformerStashBoxModal";
|
import PerformerStashBoxModal, { IStashBox } from "./PerformerStashBoxModal";
|
||||||
|
|
@ -97,7 +97,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
||||||
|
|
||||||
const [scrapedPerformer, setScrapedPerformer] =
|
const [scrapedPerformer, setScrapedPerformer] =
|
||||||
useState<GQL.ScrapedPerformer>();
|
useState<GQL.ScrapedPerformer>();
|
||||||
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
const { configuration: stashConfig } = useConfigurationContext();
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ function renderScrapedGenderRow(
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
|
field="gender"
|
||||||
title={title}
|
title={title}
|
||||||
result={result}
|
result={result}
|
||||||
renderOriginalField={() => renderScrapedGender(result)}
|
renderOriginalField={() => renderScrapedGender(result)}
|
||||||
|
|
@ -113,6 +114,7 @@ function renderScrapedCircumcisedRow(
|
||||||
return (
|
return (
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
title={title}
|
title={title}
|
||||||
|
field="circumcised"
|
||||||
result={result}
|
result={result}
|
||||||
renderOriginalField={() => renderScrapedCircumcised(result)}
|
renderOriginalField={() => renderScrapedCircumcised(result)}
|
||||||
renderNewField={() =>
|
renderNewField={() =>
|
||||||
|
|
@ -401,16 +403,19 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="name"
|
||||||
title={intl.formatMessage({ id: "name" })}
|
title={intl.formatMessage({ id: "name" })}
|
||||||
result={name}
|
result={name}
|
||||||
onChange={(value) => setName(value)}
|
onChange={(value) => setName(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="disambiguation"
|
||||||
title={intl.formatMessage({ id: "disambiguation" })}
|
title={intl.formatMessage({ id: "disambiguation" })}
|
||||||
result={disambiguation}
|
result={disambiguation}
|
||||||
onChange={(value) => setDisambiguation(value)}
|
onChange={(value) => setDisambiguation(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedTextAreaRow
|
<ScrapedTextAreaRow
|
||||||
|
field="aliases"
|
||||||
title={intl.formatMessage({ id: "aliases" })}
|
title={intl.formatMessage({ id: "aliases" })}
|
||||||
result={aliases}
|
result={aliases}
|
||||||
onChange={(value) => setAliases(value)}
|
onChange={(value) => setAliases(value)}
|
||||||
|
|
@ -421,46 +426,55 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
||||||
(value) => setGender(value)
|
(value) => setGender(value)
|
||||||
)}
|
)}
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="birthdate"
|
||||||
title={intl.formatMessage({ id: "birthdate" })}
|
title={intl.formatMessage({ id: "birthdate" })}
|
||||||
result={birthdate}
|
result={birthdate}
|
||||||
onChange={(value) => setBirthdate(value)}
|
onChange={(value) => setBirthdate(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="death_date"
|
||||||
title={intl.formatMessage({ id: "death_date" })}
|
title={intl.formatMessage({ id: "death_date" })}
|
||||||
result={deathDate}
|
result={deathDate}
|
||||||
onChange={(value) => setDeathDate(value)}
|
onChange={(value) => setDeathDate(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="ethnicity"
|
||||||
title={intl.formatMessage({ id: "ethnicity" })}
|
title={intl.formatMessage({ id: "ethnicity" })}
|
||||||
result={ethnicity}
|
result={ethnicity}
|
||||||
onChange={(value) => setEthnicity(value)}
|
onChange={(value) => setEthnicity(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedCountryRow
|
<ScrapedCountryRow
|
||||||
|
field="country"
|
||||||
title={intl.formatMessage({ id: "country" })}
|
title={intl.formatMessage({ id: "country" })}
|
||||||
result={country}
|
result={country}
|
||||||
onChange={(value) => setCountry(value)}
|
onChange={(value) => setCountry(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="hair_color"
|
||||||
title={intl.formatMessage({ id: "hair_color" })}
|
title={intl.formatMessage({ id: "hair_color" })}
|
||||||
result={hairColor}
|
result={hairColor}
|
||||||
onChange={(value) => setHairColor(value)}
|
onChange={(value) => setHairColor(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="eye_color"
|
||||||
title={intl.formatMessage({ id: "eye_color" })}
|
title={intl.formatMessage({ id: "eye_color" })}
|
||||||
result={eyeColor}
|
result={eyeColor}
|
||||||
onChange={(value) => setEyeColor(value)}
|
onChange={(value) => setEyeColor(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="weight"
|
||||||
title={intl.formatMessage({ id: "weight" })}
|
title={intl.formatMessage({ id: "weight" })}
|
||||||
result={weight}
|
result={weight}
|
||||||
onChange={(value) => setWeight(value)}
|
onChange={(value) => setWeight(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="height"
|
||||||
title={intl.formatMessage({ id: "height" })}
|
title={intl.formatMessage({ id: "height" })}
|
||||||
result={height}
|
result={height}
|
||||||
onChange={(value) => setHeight(value)}
|
onChange={(value) => setHeight(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="penis_length"
|
||||||
title={intl.formatMessage({ id: "penis_length" })}
|
title={intl.formatMessage({ id: "penis_length" })}
|
||||||
result={penisLength}
|
result={penisLength}
|
||||||
onChange={(value) => setPenisLength(value)}
|
onChange={(value) => setPenisLength(value)}
|
||||||
|
|
@ -471,42 +485,50 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
||||||
(value) => setCircumcised(value)
|
(value) => setCircumcised(value)
|
||||||
)}
|
)}
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="measurements"
|
||||||
title={intl.formatMessage({ id: "measurements" })}
|
title={intl.formatMessage({ id: "measurements" })}
|
||||||
result={measurements}
|
result={measurements}
|
||||||
onChange={(value) => setMeasurements(value)}
|
onChange={(value) => setMeasurements(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="fake_tits"
|
||||||
title={intl.formatMessage({ id: "fake_tits" })}
|
title={intl.formatMessage({ id: "fake_tits" })}
|
||||||
result={fakeTits}
|
result={fakeTits}
|
||||||
onChange={(value) => setFakeTits(value)}
|
onChange={(value) => setFakeTits(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="career_length"
|
||||||
title={intl.formatMessage({ id: "career_length" })}
|
title={intl.formatMessage({ id: "career_length" })}
|
||||||
result={careerLength}
|
result={careerLength}
|
||||||
onChange={(value) => setCareerLength(value)}
|
onChange={(value) => setCareerLength(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedTextAreaRow
|
<ScrapedTextAreaRow
|
||||||
|
field="tattoos"
|
||||||
title={intl.formatMessage({ id: "tattoos" })}
|
title={intl.formatMessage({ id: "tattoos" })}
|
||||||
result={tattoos}
|
result={tattoos}
|
||||||
onChange={(value) => setTattoos(value)}
|
onChange={(value) => setTattoos(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedTextAreaRow
|
<ScrapedTextAreaRow
|
||||||
|
field="piercings"
|
||||||
title={intl.formatMessage({ id: "piercings" })}
|
title={intl.formatMessage({ id: "piercings" })}
|
||||||
result={piercings}
|
result={piercings}
|
||||||
onChange={(value) => setPiercings(value)}
|
onChange={(value) => setPiercings(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStringListRow
|
<ScrapedStringListRow
|
||||||
|
field="urls"
|
||||||
title={intl.formatMessage({ id: "urls" })}
|
title={intl.formatMessage({ id: "urls" })}
|
||||||
result={urls}
|
result={urls}
|
||||||
onChange={(value) => setURLs(value)}
|
onChange={(value) => setURLs(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedTextAreaRow
|
<ScrapedTextAreaRow
|
||||||
|
field="details"
|
||||||
title={intl.formatMessage({ id: "details" })}
|
title={intl.formatMessage({ id: "details" })}
|
||||||
result={details}
|
result={details}
|
||||||
onChange={(value) => setDetails(value)}
|
onChange={(value) => setDetails(value)}
|
||||||
/>
|
/>
|
||||||
{scrapedTagsRow}
|
{scrapedTagsRow}
|
||||||
<ScrapedImagesRow
|
<ScrapedImagesRow
|
||||||
|
field="image"
|
||||||
title={intl.formatMessage({ id: "performer_image" })}
|
title={intl.formatMessage({ id: "performer_image" })}
|
||||||
className="performer-image"
|
className="performer-image"
|
||||||
result={image}
|
result={image}
|
||||||
|
|
@ -514,6 +536,7 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
||||||
onChange={(value) => setImage(value)}
|
onChange={(value) => setImage(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="remote_site_id"
|
||||||
title={intl.formatMessage({ id: "stash_id" })}
|
title={intl.formatMessage({ id: "stash_id" })}
|
||||||
result={remoteSiteID}
|
result={remoteSiteID}
|
||||||
locked
|
locked
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { useFindPerformer } from "../../core/StashService";
|
import { useFindPerformer } from "../../core/StashService";
|
||||||
import { PerformerCard } from "./PerformerCard";
|
import { PerformerCard } from "./PerformerCard";
|
||||||
import { ConfigurationContext } from "../../hooks/Config";
|
import { useConfigurationContext } from "../../hooks/Config";
|
||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
|
|
||||||
interface IPeromerPopoverCardProps {
|
interface IPeromerPopoverCardProps {
|
||||||
|
|
@ -49,7 +49,7 @@ export const PerformerPopover: React.FC<IPeroformerPopoverProps> = ({
|
||||||
placement = "top",
|
placement = "top",
|
||||||
target,
|
target,
|
||||||
}) => {
|
}) => {
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
|
|
||||||
const showPerformerCardOnHover = config?.ui.showTagCardOnHover ?? true;
|
const showPerformerCardOnHover = config?.ui.showTagCardOnHover ?? true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
queryFindPerformersByIDForSelect,
|
queryFindPerformersByIDForSelect,
|
||||||
queryFindPerformersForSelect,
|
queryFindPerformersForSelect,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { defaultMaxOptionsShown } from "src/core/config";
|
import { defaultMaxOptionsShown } from "src/core/config";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
|
@ -82,7 +82,7 @@ const _PerformerSelect: React.FC<
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const [createPerformer] = usePerformerCreate();
|
const [createPerformer] = usePerformerCreate();
|
||||||
|
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const maxOptionsShown =
|
const maxOptionsShown =
|
||||||
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React, {
|
import React, {
|
||||||
KeyboardEvent,
|
KeyboardEvent,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
|
|
@ -31,7 +30,7 @@ import {
|
||||||
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { ScenePlayerScrubber } from "./ScenePlayerScrubber";
|
import { ScenePlayerScrubber } from "./ScenePlayerScrubber";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import {
|
import {
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
InteractiveContext,
|
InteractiveContext,
|
||||||
|
|
@ -240,7 +239,7 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = PatchComponent(
|
||||||
onNext,
|
onNext,
|
||||||
onPrevious,
|
onPrevious,
|
||||||
}) => {
|
}) => {
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const interfaceConfig = configuration?.interface;
|
const interfaceConfig = configuration?.interface;
|
||||||
const uiConfig = configuration?.ui;
|
const uiConfig = configuration?.ui;
|
||||||
const videoRef = useRef<HTMLDivElement>(null);
|
const videoRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { useScenesDestroy } from "src/core/StashService";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { ModalComponent } from "src/components/Shared/Modal";
|
import { ModalComponent } from "src/components/Shared/Modal";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { objectPath } from "src/core/files";
|
import { objectPath } from "src/core/files";
|
||||||
|
|
@ -34,7 +34,7 @@ export const DeleteScenesDialog: React.FC<IDeleteSceneDialogProps> = (
|
||||||
{ count: props.selected.length, singularEntity, pluralEntity }
|
{ count: props.selected.length, singularEntity, pluralEntity }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
|
|
||||||
const [deleteFile, setDeleteFile] = useState<boolean>(
|
const [deleteFile, setDeleteFile] = useState<boolean>(
|
||||||
config?.defaults.deleteFile ?? false
|
config?.defaults.deleteFile ?? false
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,11 @@ import * as GQL from "src/core/generated-graphql";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { GalleryLink, TagLink, SceneMarkerLink } from "../Shared/TagLink";
|
import { GalleryLink, TagLink, SceneMarkerLink } from "../Shared/TagLink";
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { SweatDrops } from "../Shared/SweatDrops";
|
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
import NavUtils from "src/utils/navigation";
|
import NavUtils from "src/utils/navigation";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||||
import { GridCard } from "../Shared/GridCard/GridCard";
|
import { GridCard } from "../Shared/GridCard/GridCard";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
|
|
@ -30,6 +29,7 @@ import { PatchComponent } from "src/patch";
|
||||||
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
||||||
import { GroupTag } from "../Groups/GroupTag";
|
import { GroupTag } from "../Groups/GroupTag";
|
||||||
import { FileSize } from "../Shared/FileSize";
|
import { FileSize } from "../Shared/FileSize";
|
||||||
|
import { OCounterButton } from "../Shared/CountButton";
|
||||||
|
|
||||||
interface IScenePreviewProps {
|
interface IScenePreviewProps {
|
||||||
isPortrait: boolean;
|
isPortrait: boolean;
|
||||||
|
|
@ -218,16 +218,7 @@ const SceneCardPopovers = PatchComponent(
|
||||||
|
|
||||||
function maybeRenderOCounter() {
|
function maybeRenderOCounter() {
|
||||||
if (props.scene.o_counter) {
|
if (props.scene.o_counter) {
|
||||||
return (
|
return <OCounterButton value={props.scene.o_counter} />;
|
||||||
<div className="o-count">
|
|
||||||
<Button className="minimal">
|
|
||||||
<span className="fa-icon">
|
|
||||||
<SweatDrops />
|
|
||||||
</span>
|
|
||||||
<span>{props.scene.o_counter}</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -353,7 +344,7 @@ const SceneCardImage = PatchComponent(
|
||||||
"SceneCard.Image",
|
"SceneCard.Image",
|
||||||
(props: ISceneCardProps) => {
|
(props: ISceneCardProps) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const cont = configuration?.interface.continuePlaylistDefault ?? false;
|
const cont = configuration?.interface.continuePlaylistDefault ?? false;
|
||||||
|
|
||||||
const file = useMemo(
|
const file = useMemo(
|
||||||
|
|
@ -437,7 +428,7 @@ const SceneCardImage = PatchComponent(
|
||||||
export const SceneCard = PatchComponent(
|
export const SceneCard = PatchComponent(
|
||||||
"SceneCard",
|
"SceneCard",
|
||||||
(props: ISceneCardProps) => {
|
(props: ISceneCardProps) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
const file = useMemo(
|
const file = useMemo(
|
||||||
() => (props.scene.files.length > 0 ? props.scene.files[0] : undefined),
|
() => (props.scene.files.length > 0 ? props.scene.files[0] : undefined),
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { faBan, faMinus } from "@fortawesome/free-solid-svg-icons";
|
import { faBan, faMinus, faThumbsUp } from "@fortawesome/free-solid-svg-icons";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Button, ButtonGroup, Dropdown, DropdownButton } from "react-bootstrap";
|
import { Button, ButtonGroup, Dropdown, DropdownButton } from "react-bootstrap";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||||
import { SweatDrops } from "src/components/Shared/SweatDrops";
|
import { SweatDrops } from "src/components/Shared/SweatDrops";
|
||||||
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
export interface IOCounterButtonProps {
|
export interface IOCounterButtonProps {
|
||||||
value: number;
|
value: number;
|
||||||
|
|
@ -17,6 +18,12 @@ export const OCounterButton: React.FC<IOCounterButtonProps> = (
|
||||||
props: IOCounterButtonProps
|
props: IOCounterButtonProps
|
||||||
) => {
|
) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { configuration } = useConfigurationContext();
|
||||||
|
const { sfwContentMode } = configuration.interface;
|
||||||
|
|
||||||
|
const icon = !sfwContentMode ? <SweatDrops /> : <Icon icon={faThumbsUp} />;
|
||||||
|
const messageID = !sfwContentMode ? "o_count" : "o_count_sfw";
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
async function increment() {
|
async function increment() {
|
||||||
|
|
@ -44,9 +51,9 @@ export const OCounterButton: React.FC<IOCounterButtonProps> = (
|
||||||
className="minimal pr-1"
|
className="minimal pr-1"
|
||||||
onClick={increment}
|
onClick={increment}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
title={intl.formatMessage({ id: "o_counter" })}
|
title={intl.formatMessage({ id: messageID })}
|
||||||
>
|
>
|
||||||
<SweatDrops />
|
{icon}
|
||||||
<span className="ml-2">{props.value}</span>
|
<span className="ml-2">{props.value}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
useMemo,
|
useMemo,
|
||||||
useContext,
|
|
||||||
useRef,
|
useRef,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
@ -32,7 +31,7 @@ import SceneQueue, { QueuedScene } from "src/models/sceneQueue";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import { OrganizedButton } from "./OrganizedButton";
|
import { OrganizedButton } from "./OrganizedButton";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { getPlayerPosition } from "src/components/ScenePlayer/util";
|
import { getPlayerPosition } from "src/components/ScenePlayer/util";
|
||||||
import {
|
import {
|
||||||
faEllipsisV,
|
faEllipsisV,
|
||||||
|
|
@ -184,7 +183,7 @@ const ScenePage: React.FC<IProps> = PatchComponent("ScenePage", (props) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [updateScene] = useSceneUpdate();
|
const [updateScene] = useSceneUpdate();
|
||||||
const [generateScreenshot] = useSceneGenerateScreenshot();
|
const [generateScreenshot] = useSceneGenerateScreenshot();
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
const [showDraftModal, setShowDraftModal] = useState(false);
|
const [showDraftModal, setShowDraftModal] = useState(false);
|
||||||
const boxes = configuration?.general?.stashBoxes ?? [];
|
const boxes = configuration?.general?.stashBoxes ?? [];
|
||||||
|
|
@ -689,7 +688,7 @@ const SceneLoader: React.FC<RouteComponentProps<ISceneParams>> = ({
|
||||||
match,
|
match,
|
||||||
}) => {
|
}) => {
|
||||||
const { id } = match.params;
|
const { id } = match.params;
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const { data, loading, error } = useFindScene(id);
|
const { data, loading, error } = useFindScene(id);
|
||||||
|
|
||||||
const [scene, setScene] = useState<GQL.SceneDataFragment>();
|
const [scene, setScene] = useState<GQL.SceneDataFragment>();
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import ImageUtils from "src/utils/image";
|
||||||
import { getStashIDs } from "src/utils/stashIds";
|
import { getStashIDs } from "src/utils/stashIds";
|
||||||
import { useFormik } from "formik";
|
import { useFormik } from "formik";
|
||||||
import { Prompt } from "react-router-dom";
|
import { Prompt } from "react-router-dom";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { IGroupEntry, SceneGroupTable } from "./SceneGroupTable";
|
import { IGroupEntry, SceneGroupTable } from "./SceneGroupTable";
|
||||||
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
|
|
@ -103,7 +103,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||||
setStudio(scene.studio ?? null);
|
setStudio(scene.studio ?? null);
|
||||||
}, [scene.studio]);
|
}, [scene.studio]);
|
||||||
|
|
||||||
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
const { configuration: stashConfig } = useConfigurationContext();
|
||||||
|
|
||||||
// Network state
|
// Network state
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
useSceneResetActivity,
|
useSceneResetActivity,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { TextField } from "src/utils/field";
|
import { TextField } from "src/utils/field";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
|
|
@ -172,6 +173,9 @@ export const SceneHistoryPanel: React.FC<ISceneHistoryProps> = ({ scene }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
|
|
||||||
|
const { configuration } = useConfigurationContext();
|
||||||
|
const { sfwContentMode } = configuration.interface;
|
||||||
|
|
||||||
const [dialogs, setDialogs] = React.useState({
|
const [dialogs, setDialogs] = React.useState({
|
||||||
playHistory: false,
|
playHistory: false,
|
||||||
oHistory: false,
|
oHistory: false,
|
||||||
|
|
@ -299,6 +303,9 @@ export const SceneHistoryPanel: React.FC<ISceneHistoryProps> = ({ scene }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderDialogs() {
|
function maybeRenderDialogs() {
|
||||||
|
const clearHistoryMessageID = sfwContentMode
|
||||||
|
? "dialogs.clear_o_history_confirm_sfw"
|
||||||
|
: "dialogs.clear_play_history_confirm";
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AlertModal
|
<AlertModal
|
||||||
|
|
@ -312,7 +319,7 @@ export const SceneHistoryPanel: React.FC<ISceneHistoryProps> = ({ scene }) => {
|
||||||
/>
|
/>
|
||||||
<AlertModal
|
<AlertModal
|
||||||
show={dialogs.oHistory}
|
show={dialogs.oHistory}
|
||||||
text={intl.formatMessage({ id: "dialogs.clear_o_history_confirm" })}
|
text={intl.formatMessage({ id: clearHistoryMessageID })}
|
||||||
confirmButtonText={intl.formatMessage({ id: "actions.clear" })}
|
confirmButtonText={intl.formatMessage({ id: "actions.clear" })}
|
||||||
onConfirm={() => handleClearODates()}
|
onConfirm={() => handleClearODates()}
|
||||||
onCancel={() => setDialogPartial({ oHistory: false })}
|
onCancel={() => setDialogPartial({ oHistory: false })}
|
||||||
|
|
@ -351,6 +358,11 @@ export const SceneHistoryPanel: React.FC<ISceneHistoryProps> = ({ scene }) => {
|
||||||
) as string[];
|
) as string[];
|
||||||
const oHistory = (scene.o_history ?? []).filter((h) => h != null) as string[];
|
const oHistory = (scene.o_history ?? []).filter((h) => h != null) as string[];
|
||||||
|
|
||||||
|
const oHistoryMessageID = sfwContentMode ? "o_history_sfw" : "o_history";
|
||||||
|
const noneMessageID = sfwContentMode
|
||||||
|
? "odate_recorded_no_sfw"
|
||||||
|
: "odate_recorded_no";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{maybeRenderDialogs()}
|
{maybeRenderDialogs()}
|
||||||
|
|
@ -401,7 +413,7 @@ export const SceneHistoryPanel: React.FC<ISceneHistoryProps> = ({ scene }) => {
|
||||||
<div className="history-header">
|
<div className="history-header">
|
||||||
<h5>
|
<h5>
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage id="o_history" />
|
<FormattedMessage id={oHistoryMessageID} />
|
||||||
<Counter count={oHistory.length} hideZero />
|
<Counter count={oHistory.length} hideZero />
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
|
|
@ -427,7 +439,7 @@ export const SceneHistoryPanel: React.FC<ISceneHistoryProps> = ({ scene }) => {
|
||||||
</div>
|
</div>
|
||||||
<History
|
<History
|
||||||
history={oHistory}
|
history={oHistory}
|
||||||
noneID="odate_recorded_no"
|
noneID={noneMessageID}
|
||||||
unknownDate={scene.created_at}
|
unknownDate={scene.created_at}
|
||||||
onRemove={(t) => handleDeleteODate(t)}
|
onRemove={(t) => handleDeleteODate(t)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -218,32 +218,38 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="title"
|
||||||
title={intl.formatMessage({ id: "title" })}
|
title={intl.formatMessage({ id: "title" })}
|
||||||
result={title}
|
result={title}
|
||||||
onChange={(value) => setTitle(value)}
|
onChange={(value) => setTitle(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="code"
|
||||||
title={intl.formatMessage({ id: "scene_code" })}
|
title={intl.formatMessage({ id: "scene_code" })}
|
||||||
result={code}
|
result={code}
|
||||||
onChange={(value) => setCode(value)}
|
onChange={(value) => setCode(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStringListRow
|
<ScrapedStringListRow
|
||||||
|
field="urls"
|
||||||
title={intl.formatMessage({ id: "urls" })}
|
title={intl.formatMessage({ id: "urls" })}
|
||||||
result={urls}
|
result={urls}
|
||||||
onChange={(value) => setURLs(value)}
|
onChange={(value) => setURLs(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="date"
|
||||||
title={intl.formatMessage({ id: "date" })}
|
title={intl.formatMessage({ id: "date" })}
|
||||||
placeholder="YYYY-MM-DD"
|
placeholder="YYYY-MM-DD"
|
||||||
result={date}
|
result={date}
|
||||||
onChange={(value) => setDate(value)}
|
onChange={(value) => setDate(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="director"
|
||||||
title={intl.formatMessage({ id: "director" })}
|
title={intl.formatMessage({ id: "director" })}
|
||||||
result={director}
|
result={director}
|
||||||
onChange={(value) => setDirector(value)}
|
onChange={(value) => setDirector(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStudioRow
|
<ScrapedStudioRow
|
||||||
|
field="studio"
|
||||||
title={intl.formatMessage({ id: "studios" })}
|
title={intl.formatMessage({ id: "studios" })}
|
||||||
result={studio}
|
result={studio}
|
||||||
onChange={(value) => setStudio(value)}
|
onChange={(value) => setStudio(value)}
|
||||||
|
|
@ -251,6 +257,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||||
onCreateNew={createNewStudio}
|
onCreateNew={createNewStudio}
|
||||||
/>
|
/>
|
||||||
<ScrapedPerformersRow
|
<ScrapedPerformersRow
|
||||||
|
field="performers"
|
||||||
title={intl.formatMessage({ id: "performers" })}
|
title={intl.formatMessage({ id: "performers" })}
|
||||||
result={performers}
|
result={performers}
|
||||||
onChange={(value) => setPerformers(value)}
|
onChange={(value) => setPerformers(value)}
|
||||||
|
|
@ -259,6 +266,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||||
ageFromDate={date.useNewValue ? date.newValue : date.originalValue}
|
ageFromDate={date.useNewValue ? date.newValue : date.originalValue}
|
||||||
/>
|
/>
|
||||||
<ScrapedGroupsRow
|
<ScrapedGroupsRow
|
||||||
|
field="groups"
|
||||||
title={intl.formatMessage({ id: "groups" })}
|
title={intl.formatMessage({ id: "groups" })}
|
||||||
result={groups}
|
result={groups}
|
||||||
onChange={(value) => setGroups(value)}
|
onChange={(value) => setGroups(value)}
|
||||||
|
|
@ -267,17 +275,20 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||||
/>
|
/>
|
||||||
{scrapedTagsRow}
|
{scrapedTagsRow}
|
||||||
<ScrapedTextAreaRow
|
<ScrapedTextAreaRow
|
||||||
|
field="details"
|
||||||
title={intl.formatMessage({ id: "details" })}
|
title={intl.formatMessage({ id: "details" })}
|
||||||
result={details}
|
result={details}
|
||||||
onChange={(value) => setDetails(value)}
|
onChange={(value) => setDetails(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="stash_ids"
|
||||||
title={intl.formatMessage({ id: "stash_id" })}
|
title={intl.formatMessage({ id: "stash_id" })}
|
||||||
result={stashID}
|
result={stashID}
|
||||||
locked
|
locked
|
||||||
onChange={(value) => setStashID(value)}
|
onChange={(value) => setStashID(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedImageRow
|
<ScrapedImageRow
|
||||||
|
field="cover_image"
|
||||||
title={intl.formatMessage({ id: "cover_image" })}
|
title={intl.formatMessage({ id: "cover_image" })}
|
||||||
className="scene-cover"
|
className="scene-cover"
|
||||||
result={image}
|
result={image}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useCallback, useContext, useEffect, useMemo } from "react";
|
import React, { useCallback, useEffect, useMemo } from "react";
|
||||||
import cloneDeep from "lodash-es/cloneDeep";
|
import cloneDeep from "lodash-es/cloneDeep";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
@ -18,7 +18,7 @@ import { ExportDialog } from "../Shared/ExportDialog";
|
||||||
import { SceneCardsGrid } from "./SceneCardsGrid";
|
import { SceneCardsGrid } from "./SceneCardsGrid";
|
||||||
import { TaggerContext } from "../Tagger/context";
|
import { TaggerContext } from "../Tagger/context";
|
||||||
import { IdentifyDialog } from "../Dialogs/IdentifyDialog/IdentifyDialog";
|
import { IdentifyDialog } from "../Dialogs/IdentifyDialog/IdentifyDialog";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import {
|
import {
|
||||||
faPencil,
|
faPencil,
|
||||||
faPlay,
|
faPlay,
|
||||||
|
|
@ -110,7 +110,7 @@ function renderMetadataByline(result: GQL.FindScenesQueryResult) {
|
||||||
function usePlayScene() {
|
function usePlayScene() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const { configuration: config } = useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
const cont = config?.interface.continuePlaylistDefault ?? false;
|
const cont = config?.interface.continuePlaylistDefault ?? false;
|
||||||
const autoPlay = config?.interface.autostartVideoOnPlaySelected ?? false;
|
const autoPlay = config?.interface.autostartVideoOnPlaySelected ?? false;
|
||||||
|
|
||||||
|
|
@ -502,7 +502,7 @@ export const FilteredSceneList = (props: IFilteredScenes) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { filter, setFilter, loading: filterLoading } = filterState;
|
const { filter, setFilter } = filterState;
|
||||||
|
|
||||||
const { effectiveFilter, result, cachedResult, items, totalCount } =
|
const { effectiveFilter, result, cachedResult, items, totalCount } =
|
||||||
queryResult;
|
queryResult;
|
||||||
|
|
@ -709,7 +709,7 @@ export const FilteredSceneList = (props: IFilteredScenes) => {
|
||||||
];
|
];
|
||||||
|
|
||||||
// render
|
// render
|
||||||
if (filterLoading || sidebarStateLoading) return null;
|
if (sidebarStateLoading) return null;
|
||||||
|
|
||||||
const operations = (
|
const operations = (
|
||||||
<SceneListOperations
|
<SceneListOperations
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Button, ButtonGroup } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
|
|
@ -6,7 +6,7 @@ import { TagLink } from "../Shared/TagLink";
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import NavUtils from "src/utils/navigation";
|
import NavUtils from "src/utils/navigation";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { GridCard } from "../Shared/GridCard/GridCard";
|
import { GridCard } from "../Shared/GridCard/GridCard";
|
||||||
import { faTag } from "@fortawesome/free-solid-svg-icons";
|
import { faTag } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { markerTitle } from "src/core/markers";
|
import { markerTitle } from "src/core/markers";
|
||||||
|
|
@ -109,7 +109,7 @@ const SceneMarkerCardDetails = (props: ISceneMarkerCardProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const SceneMarkerCardImage = (props: ISceneMarkerCardProps) => {
|
const SceneMarkerCardImage = (props: ISceneMarkerCardProps) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
const file = useMemo(
|
const file = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
import React, {
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import Gallery, {
|
import Gallery, {
|
||||||
GalleryI,
|
GalleryI,
|
||||||
PhotoProps,
|
PhotoProps,
|
||||||
RenderImageProps,
|
RenderImageProps,
|
||||||
} from "react-photo-gallery";
|
} from "react-photo-gallery";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
import { Link, useHistory } from "react-router-dom";
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
|
|
@ -46,7 +40,7 @@ interface IExtraProps {
|
||||||
export const MarkerWallItem: React.FC<
|
export const MarkerWallItem: React.FC<
|
||||||
RenderImageProps<IMarkerPhoto> & IExtraProps
|
RenderImageProps<IMarkerPhoto> & IExtraProps
|
||||||
> = (props: RenderImageProps<IMarkerPhoto> & IExtraProps) => {
|
> = (props: RenderImageProps<IMarkerPhoto> & IExtraProps) => {
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const playSound = configuration?.interface.soundOnPreview ?? false;
|
const playSound = configuration?.interface.soundOnPreview ?? false;
|
||||||
const showTitle = configuration?.interface.wallShowTitle ?? false;
|
const showTitle = configuration?.interface.wallShowTitle ?? false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -372,27 +372,32 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="title"
|
||||||
title={intl.formatMessage({ id: "title" })}
|
title={intl.formatMessage({ id: "title" })}
|
||||||
result={title}
|
result={title}
|
||||||
onChange={(value) => setTitle(value)}
|
onChange={(value) => setTitle(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="code"
|
||||||
title={intl.formatMessage({ id: "scene_code" })}
|
title={intl.formatMessage({ id: "scene_code" })}
|
||||||
result={code}
|
result={code}
|
||||||
onChange={(value) => setCode(value)}
|
onChange={(value) => setCode(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStringListRow
|
<ScrapedStringListRow
|
||||||
|
field="urls"
|
||||||
title={intl.formatMessage({ id: "urls" })}
|
title={intl.formatMessage({ id: "urls" })}
|
||||||
result={url}
|
result={url}
|
||||||
onChange={(value) => setURL(value)}
|
onChange={(value) => setURL(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
|
field="date"
|
||||||
title={intl.formatMessage({ id: "date" })}
|
title={intl.formatMessage({ id: "date" })}
|
||||||
placeholder="YYYY-MM-DD"
|
placeholder="YYYY-MM-DD"
|
||||||
result={date}
|
result={date}
|
||||||
onChange={(value) => setDate(value)}
|
onChange={(value) => setDate(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
|
field="rating"
|
||||||
title={intl.formatMessage({ id: "rating" })}
|
title={intl.formatMessage({ id: "rating" })}
|
||||||
result={rating}
|
result={rating}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
|
|
@ -404,6 +409,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||||
onChange={(value) => setRating(value)}
|
onChange={(value) => setRating(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
|
field="o_count"
|
||||||
title={intl.formatMessage({ id: "o_count" })}
|
title={intl.formatMessage({ id: "o_count" })}
|
||||||
result={oCounter}
|
result={oCounter}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
|
|
@ -425,6 +431,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||||
onChange={(value) => setOCounter(value)}
|
onChange={(value) => setOCounter(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
|
field="play_count"
|
||||||
title={intl.formatMessage({ id: "play_count" })}
|
title={intl.formatMessage({ id: "play_count" })}
|
||||||
result={playCount}
|
result={playCount}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
|
|
@ -446,6 +453,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||||
onChange={(value) => setPlayCount(value)}
|
onChange={(value) => setPlayCount(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
|
field="play_duration"
|
||||||
title={intl.formatMessage({ id: "play_duration" })}
|
title={intl.formatMessage({ id: "play_duration" })}
|
||||||
result={playDuration}
|
result={playDuration}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
|
|
@ -469,6 +477,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||||
onChange={(value) => setPlayDuration(value)}
|
onChange={(value) => setPlayDuration(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
|
field="galleries"
|
||||||
title={intl.formatMessage({ id: "galleries" })}
|
title={intl.formatMessage({ id: "galleries" })}
|
||||||
result={galleries}
|
result={galleries}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
|
|
@ -492,32 +501,38 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||||
onChange={(value) => setGalleries(value)}
|
onChange={(value) => setGalleries(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStudioRow
|
<ScrapedStudioRow
|
||||||
|
field="studio"
|
||||||
title={intl.formatMessage({ id: "studios" })}
|
title={intl.formatMessage({ id: "studios" })}
|
||||||
result={studio}
|
result={studio}
|
||||||
onChange={(value) => setStudio(value)}
|
onChange={(value) => setStudio(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedPerformersRow
|
<ScrapedPerformersRow
|
||||||
|
field="performers"
|
||||||
title={intl.formatMessage({ id: "performers" })}
|
title={intl.formatMessage({ id: "performers" })}
|
||||||
result={performers}
|
result={performers}
|
||||||
onChange={(value) => setPerformers(value)}
|
onChange={(value) => setPerformers(value)}
|
||||||
ageFromDate={date.useNewValue ? date.newValue : date.originalValue}
|
ageFromDate={date.useNewValue ? date.newValue : date.originalValue}
|
||||||
/>
|
/>
|
||||||
<ScrapedGroupsRow
|
<ScrapedGroupsRow
|
||||||
|
field="groups"
|
||||||
title={intl.formatMessage({ id: "groups" })}
|
title={intl.formatMessage({ id: "groups" })}
|
||||||
result={groups}
|
result={groups}
|
||||||
onChange={(value) => setGroups(value)}
|
onChange={(value) => setGroups(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedTagsRow
|
<ScrapedTagsRow
|
||||||
|
field="tags"
|
||||||
title={intl.formatMessage({ id: "tags" })}
|
title={intl.formatMessage({ id: "tags" })}
|
||||||
result={tags}
|
result={tags}
|
||||||
onChange={(value) => setTags(value)}
|
onChange={(value) => setTags(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedTextAreaRow
|
<ScrapedTextAreaRow
|
||||||
|
field="details"
|
||||||
title={intl.formatMessage({ id: "details" })}
|
title={intl.formatMessage({ id: "details" })}
|
||||||
result={details}
|
result={details}
|
||||||
onChange={(value) => setDetails(value)}
|
onChange={(value) => setDetails(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
|
field="organized"
|
||||||
title={intl.formatMessage({ id: "organized" })}
|
title={intl.formatMessage({ id: "organized" })}
|
||||||
result={organized}
|
result={organized}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
|
|
@ -539,6 +554,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||||
onChange={(value) => setOrganized(value)}
|
onChange={(value) => setOrganized(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
|
field="stash_ids"
|
||||||
title={intl.formatMessage({ id: "stash_id" })}
|
title={intl.formatMessage({ id: "stash_id" })}
|
||||||
result={stashIDs}
|
result={stashIDs}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
|
|
@ -550,6 +566,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||||
onChange={(value) => setStashIDs(value)}
|
onChange={(value) => setStashIDs(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedImageRow
|
<ScrapedImageRow
|
||||||
|
field="cover_image"
|
||||||
title={intl.formatMessage({ id: "cover_image" })}
|
title={intl.formatMessage({ id: "cover_image" })}
|
||||||
className="scene-cover"
|
className="scene-cover"
|
||||||
result={image}
|
result={image}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
queryFindScenesForSelect,
|
queryFindScenesForSelect,
|
||||||
queryFindScenesByIDForSelect,
|
queryFindScenesByIDForSelect,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { defaultMaxOptionsShown } from "src/core/config";
|
import { defaultMaxOptionsShown } from "src/core/config";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
|
@ -66,7 +66,7 @@ const sceneSelectSort = PatchFunction(
|
||||||
const _SceneSelect: React.FC<
|
const _SceneSelect: React.FC<
|
||||||
IFilterProps & IFilterValueProps<Scene> & ExtraSceneProps
|
IFilterProps & IFilterValueProps<Scene> & ExtraSceneProps
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const maxOptionsShown =
|
const maxOptionsShown =
|
||||||
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
import React, {
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
import Gallery, {
|
import Gallery, {
|
||||||
|
|
@ -12,7 +6,7 @@ import Gallery, {
|
||||||
PhotoProps,
|
PhotoProps,
|
||||||
RenderImageProps,
|
RenderImageProps,
|
||||||
} from "react-photo-gallery";
|
} from "react-photo-gallery";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
import { Link, useHistory } from "react-router-dom";
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
|
|
@ -35,7 +29,7 @@ export const SceneWallItem: React.FC<
|
||||||
> = (props: RenderImageProps<IScenePhoto> & IExtraProps) => {
|
> = (props: RenderImageProps<IScenePhoto> & IExtraProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const playSound = configuration?.interface.soundOnPreview ?? false;
|
const playSound = configuration?.interface.soundOnPreview ?? false;
|
||||||
const showTitle = configuration?.interface.wallShowTitle ?? false;
|
const showTitle = configuration?.interface.wallShowTitle ?? false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,14 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
|
||||||
<option value="zh-CN">简体中文 (中国)</option>
|
<option value="zh-CN">简体中文 (中国)</option>
|
||||||
</SelectSetting>
|
</SelectSetting>
|
||||||
|
|
||||||
|
<BooleanSetting
|
||||||
|
id="sfw-content-mode"
|
||||||
|
headingID="config.ui.sfw_mode.heading"
|
||||||
|
subHeadingID="config.ui.sfw_mode.description"
|
||||||
|
checked={iface.sfwContentMode ?? undefined}
|
||||||
|
onChange={(v) => saveInterface({ sfwContentMode: v })}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="setting-group">
|
<div className="setting-group">
|
||||||
<div className="setting">
|
<div className="setting">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import { SettingSection } from "../SettingSection";
|
||||||
import { BooleanSetting, Setting } from "../Inputs";
|
import { BooleanSetting, Setting } 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 { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
||||||
import {
|
import {
|
||||||
faMinus,
|
faMinus,
|
||||||
|
|
@ -44,7 +44,7 @@ const CleanDialog: React.FC<ICleanDialog> = ({
|
||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
const libraryPaths = configuration?.general.stashes.map((s) => s.path);
|
const libraryPaths = configuration?.general.stashes.map((s) => s.path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { useIntl } from "react-intl";
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import { ModalComponent } from "src/components/Shared/Modal";
|
import { ModalComponent } from "src/components/Shared/Modal";
|
||||||
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
interface IDirectorySelectionDialogProps {
|
interface IDirectorySelectionDialogProps {
|
||||||
animation?: boolean;
|
animation?: boolean;
|
||||||
|
|
@ -22,7 +22,7 @@ export const DirectorySelectionDialog: React.FC<
|
||||||
IDirectorySelectionDialogProps
|
IDirectorySelectionDialogProps
|
||||||
> = ({ animation, allowEmpty = false, initialPaths = [], onClose }) => {
|
> = ({ animation, allowEmpty = false, initialPaths = [], onClose }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
const libraryPaths = configuration?.general.stashes.map((s) => s.path);
|
const libraryPaths = configuration?.general.stashes.map((s) => s.path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
mutateMetadataGenerate,
|
mutateMetadataGenerate,
|
||||||
} 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 { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { IdentifyDialog } from "../../Dialogs/IdentifyDialog/IdentifyDialog";
|
import { IdentifyDialog } from "../../Dialogs/IdentifyDialog/IdentifyDialog";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { DirectorySelectionDialog } from "./DirectorySelectionDialog";
|
import { DirectorySelectionDialog } from "./DirectorySelectionDialog";
|
||||||
|
|
@ -123,7 +123,7 @@ export const LibraryTasks: React.FC = () => {
|
||||||
|
|
||||||
type DialogOpenState = typeof dialogOpen;
|
type DialogOpenState = typeof dialogOpen;
|
||||||
|
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const [configRead, setConfigRead] = useState(false);
|
const [configRead, setConfigRead] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useContext, useCallback } from "react";
|
import React, { useState, useCallback } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
useSystemStatus,
|
useSystemStatus,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import StashConfiguration from "../Settings/StashConfiguration";
|
import StashConfiguration from "../Settings/StashConfiguration";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
||||||
|
|
@ -518,6 +518,10 @@ const SetPathsStep: React.FC<IWizardStep> = ({ goBack, next }) => {
|
||||||
const [stashes, setStashes] = useState<GQL.StashConfig[]>(
|
const [stashes, setStashes] = useState<GQL.StashConfig[]>(
|
||||||
setupState.stashes ?? []
|
setupState.stashes ?? []
|
||||||
);
|
);
|
||||||
|
const [sfwContentMode, setSfwContentMode] = useState(
|
||||||
|
setupState.sfwContentMode ?? false
|
||||||
|
);
|
||||||
|
|
||||||
const [databaseFile, setDatabaseFile] = useState(
|
const [databaseFile, setDatabaseFile] = useState(
|
||||||
setupState.databaseFile ?? ""
|
setupState.databaseFile ?? ""
|
||||||
);
|
);
|
||||||
|
|
@ -555,6 +559,7 @@ const SetPathsStep: React.FC<IWizardStep> = ({ goBack, next }) => {
|
||||||
cacheLocation,
|
cacheLocation,
|
||||||
blobsLocation: storeBlobsInDatabase ? "" : blobsLocation,
|
blobsLocation: storeBlobsInDatabase ? "" : blobsLocation,
|
||||||
storeBlobsInDatabase,
|
storeBlobsInDatabase,
|
||||||
|
sfwContentMode,
|
||||||
};
|
};
|
||||||
next(input);
|
next(input);
|
||||||
}
|
}
|
||||||
|
|
@ -594,6 +599,22 @@ const SetPathsStep: React.FC<IWizardStep> = ({ goBack, next }) => {
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
<Form.Group id="sfw_content">
|
||||||
|
<h3>
|
||||||
|
<FormattedMessage id="setup.paths.sfw_content_settings" />
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage id="setup.paths.sfw_content_settings_description" />
|
||||||
|
</p>
|
||||||
|
<Card>
|
||||||
|
<Form.Check
|
||||||
|
id="use-sfw-content-mode"
|
||||||
|
checked={sfwContentMode}
|
||||||
|
label={<FormattedMessage id="setup.paths.use_sfw_content_mode" />}
|
||||||
|
onChange={() => setSfwContentMode(!sfwContentMode)}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Form.Group>
|
||||||
{overrideDatabase ? null : (
|
{overrideDatabase ? null : (
|
||||||
<DatabaseSection
|
<DatabaseSection
|
||||||
databaseFile={databaseFile}
|
databaseFile={databaseFile}
|
||||||
|
|
@ -952,8 +973,7 @@ const FinishStep: React.FC<IWizardStep> = ({ goBack }) => {
|
||||||
|
|
||||||
export const Setup: React.FC = () => {
|
export const Setup: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { configuration, loading: configLoading } =
|
const { configuration } = useConfigurationContext();
|
||||||
useContext(ConfigurationContext);
|
|
||||||
|
|
||||||
const [saveUI] = useConfigureUI();
|
const [saveUI] = useConfigureUI();
|
||||||
|
|
||||||
|
|
@ -1024,7 +1044,7 @@ export const Setup: React.FC = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configLoading || statusLoading) {
|
if (statusLoading) {
|
||||||
return <LoadingIndicator />;
|
return <LoadingIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { faEye } from "@fortawesome/free-solid-svg-icons";
|
import { faEye, faThumbsUp } from "@fortawesome/free-solid-svg-icons";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button, ButtonGroup } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import { SweatDrops } from "./SweatDrops";
|
import { SweatDrops } from "./SweatDrops";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
interface ICountButtonProps {
|
interface ICountButtonProps {
|
||||||
value: number;
|
value: number;
|
||||||
|
|
@ -63,11 +64,17 @@ export const ViewCountButton: React.FC<CountButtonPropsNoIcon> = (props) => {
|
||||||
|
|
||||||
export const OCounterButton: React.FC<CountButtonPropsNoIcon> = (props) => {
|
export const OCounterButton: React.FC<CountButtonPropsNoIcon> = (props) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { configuration } = useConfigurationContext();
|
||||||
|
const { sfwContentMode } = configuration.interface;
|
||||||
|
|
||||||
|
const icon = !sfwContentMode ? <SweatDrops /> : <Icon icon={faThumbsUp} />;
|
||||||
|
const messageID = !sfwContentMode ? "o_count" : "o_count_sfw";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CountButton
|
<CountButton
|
||||||
{...props}
|
{...props}
|
||||||
icon={<SweatDrops />}
|
icon={icon}
|
||||||
title={intl.formatMessage({ id: "o_count" })}
|
title={intl.formatMessage({ id: messageID })}
|
||||||
countTitle={intl.formatMessage({ id: "actions.view_history" })}
|
countTitle={intl.formatMessage({ id: "actions.view_history" })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
interface IDetailItem {
|
interface IDetailItem {
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
|
className?: string;
|
||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
value?: React.ReactNode;
|
value?: React.ReactNode;
|
||||||
labelTitle?: string;
|
labelTitle?: string;
|
||||||
|
|
@ -13,6 +14,7 @@ interface IDetailItem {
|
||||||
|
|
||||||
export const DetailItem: React.FC<IDetailItem> = ({
|
export const DetailItem: React.FC<IDetailItem> = ({
|
||||||
id,
|
id,
|
||||||
|
className = "",
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
labelTitle,
|
labelTitle,
|
||||||
|
|
@ -30,7 +32,7 @@ export const DetailItem: React.FC<IDetailItem> = ({
|
||||||
const sanitisedID = id.replace(/_/g, "-");
|
const sanitisedID = id.replace(/_/g, "-");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`detail-item ${id}`}>
|
<div className={`detail-item ${id} ${className}`}>
|
||||||
<span className={`detail-item-title ${sanitisedID}`} title={labelTitle}>
|
<span className={`detail-item-title ${sanitisedID}`} title={labelTitle}>
|
||||||
{message}
|
{message}
|
||||||
{fullWidth ? ":" : ""}
|
{fullWidth ? ":" : ""}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
interface IStudio {
|
interface IStudio {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -11,7 +11,7 @@ interface IStudio {
|
||||||
export const StudioOverlay: React.FC<{
|
export const StudioOverlay: React.FC<{
|
||||||
studio: IStudio | null | undefined;
|
studio: IStudio | null | undefined;
|
||||||
}> = ({ studio }) => {
|
}> = ({ studio }) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
const configValue = configuration?.interface.showStudioAsText;
|
const configValue = configuration?.interface.showStudioAsText;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,14 @@ import React from "react";
|
||||||
import { Button, OverlayTrigger, Tooltip } from "react-bootstrap";
|
import { Button, OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||||
import { FormattedNumber, useIntl } from "react-intl";
|
import { FormattedNumber, useIntl } from "react-intl";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { Icon } from "./Icon";
|
import { Icon } from "./Icon";
|
||||||
|
|
||||||
export const Count: React.FC<{
|
export const Count: React.FC<{
|
||||||
count: number;
|
count: number;
|
||||||
}> = ({ count }) => {
|
}> = ({ count }) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const abbreviateCounter = configuration?.ui.abbreviateCounters ?? false;
|
const abbreviateCounter = configuration?.ui.abbreviateCounters ?? false;
|
||||||
|
|
||||||
if (!abbreviateCounter) {
|
if (!abbreviateCounter) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import React from "react";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
|
||||||
import {
|
import {
|
||||||
defaultRatingStarPrecision,
|
defaultRatingStarPrecision,
|
||||||
defaultRatingSystemOptions,
|
defaultRatingSystemOptions,
|
||||||
|
|
@ -23,7 +22,7 @@ export interface IRatingSystemProps {
|
||||||
export const RatingSystem = PatchComponent(
|
export const RatingSystem = PatchComponent(
|
||||||
"RatingSystem",
|
"RatingSystem",
|
||||||
(props: IRatingSystemProps) => {
|
(props: IRatingSystemProps) => {
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
const ratingSystemOptions =
|
const ratingSystemOptions =
|
||||||
config?.ui.ratingSystemOptions ?? defaultRatingSystemOptions;
|
config?.ui.ratingSystemOptions ?? defaultRatingSystemOptions;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useContext } from "react";
|
import React from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import {
|
import {
|
||||||
convertToRatingFormat,
|
convertToRatingFormat,
|
||||||
|
|
@ -6,14 +6,14 @@ import {
|
||||||
RatingStarPrecision,
|
RatingStarPrecision,
|
||||||
RatingSystemType,
|
RatingSystemType,
|
||||||
} from "src/utils/rating";
|
} from "src/utils/rating";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
rating?: number | null;
|
rating?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RatingBanner: React.FC<IProps> = ({ rating }) => {
|
export const RatingBanner: React.FC<IProps> = ({ rating }) => {
|
||||||
const { configuration: config } = useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
const ratingSystemOptions =
|
const ratingSystemOptions =
|
||||||
config?.ui.ratingSystemOptions ?? defaultRatingSystemOptions;
|
config?.ui.ratingSystemOptions ?? defaultRatingSystemOptions;
|
||||||
const isLegacy =
|
const isLegacy =
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { CountrySelect } from "../CountrySelect";
|
||||||
import { StringListInput } from "../StringListInput";
|
import { StringListInput } from "../StringListInput";
|
||||||
import { ImageSelector } from "../ImageSelector";
|
import { ImageSelector } from "../ImageSelector";
|
||||||
import { ScrapeResult } from "./scrapeResult";
|
import { ScrapeResult } from "./scrapeResult";
|
||||||
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
interface IScrapedFieldProps<T> {
|
interface IScrapedFieldProps<T> {
|
||||||
result: ScrapeResult<T>;
|
result: ScrapeResult<T>;
|
||||||
|
|
@ -31,6 +32,7 @@ interface IScrapedFieldProps<T> {
|
||||||
|
|
||||||
interface IScrapedRowProps<T, V> extends IScrapedFieldProps<T> {
|
interface IScrapedRowProps<T, V> extends IScrapedFieldProps<T> {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
field: string;
|
||||||
title: string;
|
title: string;
|
||||||
renderOriginalField: (result: ScrapeResult<T>) => JSX.Element | undefined;
|
renderOriginalField: (result: ScrapeResult<T>) => JSX.Element | undefined;
|
||||||
renderNewField: (result: ScrapeResult<T>) => JSX.Element | undefined;
|
renderNewField: (result: ScrapeResult<T>) => JSX.Element | undefined;
|
||||||
|
|
@ -105,7 +107,10 @@ export const ScrapeDialogRow = <T, V>(props: IScrapedRowProps<T, V>) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className={`px-3 pt-3 ${props.className ?? ""}`}>
|
<Row
|
||||||
|
className={`px-3 pt-3 ${props.className ?? ""}`}
|
||||||
|
data-field={props.field}
|
||||||
|
>
|
||||||
<Form.Label column lg="3">
|
<Form.Label column lg="3">
|
||||||
{props.title}
|
{props.title}
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
|
|
@ -175,6 +180,8 @@ function getNameString(value: string) {
|
||||||
|
|
||||||
interface IScrapedInputGroupRowProps {
|
interface IScrapedInputGroupRowProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
field: string;
|
||||||
|
className?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
result: ScrapeResult<string>;
|
result: ScrapeResult<string>;
|
||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
|
|
@ -187,6 +194,8 @@ export const ScrapedInputGroupRow: React.FC<IScrapedInputGroupRowProps> = (
|
||||||
return (
|
return (
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
title={props.title}
|
title={props.title}
|
||||||
|
field={props.field}
|
||||||
|
className={props.className}
|
||||||
result={props.result}
|
result={props.result}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
<ScrapedInputGroup
|
<ScrapedInputGroup
|
||||||
|
|
@ -240,6 +249,7 @@ const ScrapedStringList: React.FC<IScrapedStringListProps> = (props) => {
|
||||||
|
|
||||||
interface IScrapedStringListRowProps {
|
interface IScrapedStringListRowProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
field: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
result: ScrapeResult<string[]>;
|
result: ScrapeResult<string[]>;
|
||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
|
|
@ -253,6 +263,7 @@ export const ScrapedStringListRow: React.FC<IScrapedStringListRowProps> = (
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
className="string-list-row"
|
className="string-list-row"
|
||||||
title={props.title}
|
title={props.title}
|
||||||
|
field={props.field}
|
||||||
result={props.result}
|
result={props.result}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
<ScrapedStringList
|
<ScrapedStringList
|
||||||
|
|
@ -300,6 +311,7 @@ export const ScrapedTextAreaRow: React.FC<IScrapedInputGroupRowProps> = (
|
||||||
return (
|
return (
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
title={props.title}
|
title={props.title}
|
||||||
|
field={props.field}
|
||||||
result={props.result}
|
result={props.result}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
<ScrapedTextArea
|
<ScrapedTextArea
|
||||||
|
|
@ -346,6 +358,7 @@ const ScrapedImage: React.FC<IScrapedImageProps> = (props) => {
|
||||||
|
|
||||||
interface IScrapedImageRowProps {
|
interface IScrapedImageRowProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
field: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
result: ScrapeResult<string>;
|
result: ScrapeResult<string>;
|
||||||
onChange: (value: ScrapeResult<string>) => void;
|
onChange: (value: ScrapeResult<string>) => void;
|
||||||
|
|
@ -355,6 +368,7 @@ export const ScrapedImageRow: React.FC<IScrapedImageRowProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
title={props.title}
|
title={props.title}
|
||||||
|
field={props.field}
|
||||||
result={props.result}
|
result={props.result}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
<ScrapedImage
|
<ScrapedImage
|
||||||
|
|
@ -379,6 +393,7 @@ export const ScrapedImageRow: React.FC<IScrapedImageRowProps> = (props) => {
|
||||||
|
|
||||||
interface IScrapedImagesRowProps {
|
interface IScrapedImagesRowProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
field: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
result: ScrapeResult<string>;
|
result: ScrapeResult<string>;
|
||||||
images: string[];
|
images: string[];
|
||||||
|
|
@ -397,6 +412,7 @@ export const ScrapedImagesRow: React.FC<IScrapedImagesRowProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
title={props.title}
|
title={props.title}
|
||||||
|
field={props.field}
|
||||||
result={props.result}
|
result={props.result}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
<ScrapedImage
|
<ScrapedImage
|
||||||
|
|
@ -433,6 +449,9 @@ export const ScrapeDialog: React.FC<IScrapeDialogProps> = (
|
||||||
props: IScrapeDialogProps
|
props: IScrapeDialogProps
|
||||||
) => {
|
) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { configuration } = useConfigurationContext();
|
||||||
|
const { sfwContentMode } = configuration.interface;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalComponent
|
<ModalComponent
|
||||||
show
|
show
|
||||||
|
|
@ -449,7 +468,10 @@ export const ScrapeDialog: React.FC<IScrapeDialogProps> = (
|
||||||
text: intl.formatMessage({ id: "actions.cancel" }),
|
text: intl.formatMessage({ id: "actions.cancel" }),
|
||||||
variant: "secondary",
|
variant: "secondary",
|
||||||
}}
|
}}
|
||||||
modalProps={{ size: "lg", dialogClassName: "scrape-dialog" }}
|
modalProps={{
|
||||||
|
size: "lg",
|
||||||
|
dialogClassName: `scrape-dialog ${sfwContentMode ? "sfw-mode" : ""}`,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="dialog-container">
|
<div className="dialog-container">
|
||||||
<Form>
|
<Form>
|
||||||
|
|
@ -479,6 +501,7 @@ export const ScrapeDialog: React.FC<IScrapeDialogProps> = (
|
||||||
|
|
||||||
interface IScrapedCountryRowProps {
|
interface IScrapedCountryRowProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
field: string;
|
||||||
result: ScrapeResult<string>;
|
result: ScrapeResult<string>;
|
||||||
onChange: (value: ScrapeResult<string>) => void;
|
onChange: (value: ScrapeResult<string>) => void;
|
||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
|
|
@ -487,6 +510,7 @@ interface IScrapedCountryRowProps {
|
||||||
|
|
||||||
export const ScrapedCountryRow: React.FC<IScrapedCountryRowProps> = ({
|
export const ScrapedCountryRow: React.FC<IScrapedCountryRowProps> = ({
|
||||||
title,
|
title,
|
||||||
|
field,
|
||||||
result,
|
result,
|
||||||
onChange,
|
onChange,
|
||||||
locked,
|
locked,
|
||||||
|
|
@ -494,6 +518,7 @@ export const ScrapedCountryRow: React.FC<IScrapedCountryRowProps> = ({
|
||||||
}) => (
|
}) => (
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
title={title}
|
title={title}
|
||||||
|
field={field}
|
||||||
result={result}
|
result={result}
|
||||||
renderOriginalField={() => (
|
renderOriginalField={() => (
|
||||||
<FormControl
|
<FormControl
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { uniq } from "lodash-es";
|
||||||
|
|
||||||
interface IScrapedStudioRow {
|
interface IScrapedStudioRow {
|
||||||
title: string;
|
title: string;
|
||||||
|
field: string;
|
||||||
result: ObjectScrapeResult<GQL.ScrapedStudio>;
|
result: ObjectScrapeResult<GQL.ScrapedStudio>;
|
||||||
onChange: (value: ObjectScrapeResult<GQL.ScrapedStudio>) => void;
|
onChange: (value: ObjectScrapeResult<GQL.ScrapedStudio>) => void;
|
||||||
newStudio?: GQL.ScrapedStudio;
|
newStudio?: GQL.ScrapedStudio;
|
||||||
|
|
@ -25,6 +26,7 @@ function getObjectName<T extends { name: string }>(value: T) {
|
||||||
|
|
||||||
export const ScrapedStudioRow: React.FC<IScrapedStudioRow> = ({
|
export const ScrapedStudioRow: React.FC<IScrapedStudioRow> = ({
|
||||||
title,
|
title,
|
||||||
|
field,
|
||||||
result,
|
result,
|
||||||
onChange,
|
onChange,
|
||||||
newStudio,
|
newStudio,
|
||||||
|
|
@ -73,6 +75,7 @@ export const ScrapedStudioRow: React.FC<IScrapedStudioRow> = ({
|
||||||
return (
|
return (
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
title={title}
|
title={title}
|
||||||
|
field={field}
|
||||||
result={result}
|
result={result}
|
||||||
renderOriginalField={() => renderScrapedStudio(result)}
|
renderOriginalField={() => renderScrapedStudio(result)}
|
||||||
renderNewField={() =>
|
renderNewField={() =>
|
||||||
|
|
@ -92,6 +95,7 @@ export const ScrapedStudioRow: React.FC<IScrapedStudioRow> = ({
|
||||||
|
|
||||||
interface IScrapedObjectsRow<T> {
|
interface IScrapedObjectsRow<T> {
|
||||||
title: string;
|
title: string;
|
||||||
|
field: string;
|
||||||
result: ScrapeResult<T[]>;
|
result: ScrapeResult<T[]>;
|
||||||
onChange: (value: ScrapeResult<T[]>) => void;
|
onChange: (value: ScrapeResult<T[]>) => void;
|
||||||
newObjects?: T[];
|
newObjects?: T[];
|
||||||
|
|
@ -107,6 +111,7 @@ interface IScrapedObjectsRow<T> {
|
||||||
export const ScrapedObjectsRow = <T,>(props: IScrapedObjectsRow<T>) => {
|
export const ScrapedObjectsRow = <T,>(props: IScrapedObjectsRow<T>) => {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
|
field,
|
||||||
result,
|
result,
|
||||||
onChange,
|
onChange,
|
||||||
newObjects,
|
newObjects,
|
||||||
|
|
@ -118,6 +123,7 @@ export const ScrapedObjectsRow = <T,>(props: IScrapedObjectsRow<T>) => {
|
||||||
return (
|
return (
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
title={title}
|
title={title}
|
||||||
|
field={field}
|
||||||
result={result}
|
result={result}
|
||||||
renderOriginalField={() => renderObjects(result)}
|
renderOriginalField={() => renderObjects(result)}
|
||||||
renderNewField={() =>
|
renderNewField={() =>
|
||||||
|
|
@ -142,7 +148,15 @@ type IScrapedObjectRowImpl<T> = Omit<
|
||||||
|
|
||||||
export const ScrapedPerformersRow: React.FC<
|
export const ScrapedPerformersRow: React.FC<
|
||||||
IScrapedObjectRowImpl<GQL.ScrapedPerformer> & { ageFromDate?: string | null }
|
IScrapedObjectRowImpl<GQL.ScrapedPerformer> & { ageFromDate?: string | null }
|
||||||
> = ({ title, result, onChange, newObjects, onCreateNew, ageFromDate }) => {
|
> = ({
|
||||||
|
title,
|
||||||
|
field,
|
||||||
|
result,
|
||||||
|
onChange,
|
||||||
|
newObjects,
|
||||||
|
onCreateNew,
|
||||||
|
ageFromDate,
|
||||||
|
}) => {
|
||||||
const performersCopy = useMemo(() => {
|
const performersCopy = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
newObjects?.map((p) => {
|
newObjects?.map((p) => {
|
||||||
|
|
@ -191,6 +205,7 @@ export const ScrapedPerformersRow: React.FC<
|
||||||
return (
|
return (
|
||||||
<ScrapedObjectsRow<GQL.ScrapedPerformer>
|
<ScrapedObjectsRow<GQL.ScrapedPerformer>
|
||||||
title={title}
|
title={title}
|
||||||
|
field={field}
|
||||||
result={result}
|
result={result}
|
||||||
renderObjects={renderScrapedPerformers}
|
renderObjects={renderScrapedPerformers}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|
@ -203,7 +218,7 @@ export const ScrapedPerformersRow: React.FC<
|
||||||
|
|
||||||
export const ScrapedGroupsRow: React.FC<
|
export const ScrapedGroupsRow: React.FC<
|
||||||
IScrapedObjectRowImpl<GQL.ScrapedGroup>
|
IScrapedObjectRowImpl<GQL.ScrapedGroup>
|
||||||
> = ({ title, result, onChange, newObjects, onCreateNew }) => {
|
> = ({ title, field, result, onChange, newObjects, onCreateNew }) => {
|
||||||
const groupsCopy = useMemo(() => {
|
const groupsCopy = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
newObjects?.map((p) => {
|
newObjects?.map((p) => {
|
||||||
|
|
@ -251,6 +266,7 @@ export const ScrapedGroupsRow: React.FC<
|
||||||
return (
|
return (
|
||||||
<ScrapedObjectsRow<GQL.ScrapedGroup>
|
<ScrapedObjectsRow<GQL.ScrapedGroup>
|
||||||
title={title}
|
title={title}
|
||||||
|
field={field}
|
||||||
result={result}
|
result={result}
|
||||||
renderObjects={renderScrapedGroups}
|
renderObjects={renderScrapedGroups}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|
@ -263,7 +279,7 @@ export const ScrapedGroupsRow: React.FC<
|
||||||
|
|
||||||
export const ScrapedTagsRow: React.FC<
|
export const ScrapedTagsRow: React.FC<
|
||||||
IScrapedObjectRowImpl<GQL.ScrapedTag>
|
IScrapedObjectRowImpl<GQL.ScrapedTag>
|
||||||
> = ({ title, result, onChange, newObjects, onCreateNew }) => {
|
> = ({ title, field, result, onChange, newObjects, onCreateNew }) => {
|
||||||
function renderScrapedTags(
|
function renderScrapedTags(
|
||||||
scrapeResult: ScrapeResult<GQL.ScrapedTag[]>,
|
scrapeResult: ScrapeResult<GQL.ScrapedTag[]>,
|
||||||
isNew?: boolean,
|
isNew?: boolean,
|
||||||
|
|
@ -297,6 +313,7 @@ export const ScrapedTagsRow: React.FC<
|
||||||
return (
|
return (
|
||||||
<ScrapedObjectsRow<GQL.ScrapedTag>
|
<ScrapedObjectsRow<GQL.ScrapedTag>
|
||||||
title={title}
|
title={title}
|
||||||
|
field={field}
|
||||||
result={result}
|
result={result}
|
||||||
renderObjects={renderScrapedTags}
|
renderObjects={renderScrapedTags}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ export function useScrapedTags(
|
||||||
|
|
||||||
const scrapedTagsRow = (
|
const scrapedTagsRow = (
|
||||||
<ScrapedTagsRow
|
<ScrapedTagsRow
|
||||||
|
field="tags"
|
||||||
title={intl.formatMessage({ id: "tags" })}
|
title={intl.formatMessage({ id: "tags" })}
|
||||||
result={tags}
|
result={tags}
|
||||||
onChange={(value) => setTags(value)}
|
onChange={(value) => setTags(value)}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import CreatableSelect from "react-select/creatable";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useMarkerStrings } from "src/core/StashService";
|
import { useMarkerStrings } from "src/core/StashService";
|
||||||
import { SelectComponents } from "react-select/dist/declarations/src/components";
|
import { SelectComponents } from "react-select/dist/declarations/src/components";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
import { defaultMaxOptionsShown } from "src/core/config";
|
import { defaultMaxOptionsShown } from "src/core/config";
|
||||||
import { useDebounce } from "src/hooks/debounce";
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
|
|
@ -108,7 +108,7 @@ const getSelectedItems = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||||
const LimitedSelectMenu = <T extends boolean>(
|
const LimitedSelectMenu = <T extends boolean>(
|
||||||
props: MenuListProps<Option, T, GroupBase<Option>>
|
props: MenuListProps<Option, T, GroupBase<Option>>
|
||||||
) => {
|
) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const maxOptionsShown =
|
const maxOptionsShown =
|
||||||
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
||||||
|
|
||||||
|
|
@ -496,6 +496,7 @@ export const ListSelect = <T extends {}>(props: IListSelect<T>) => {
|
||||||
|
|
||||||
type DisableOption = Option & {
|
type DisableOption = Option & {
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ICheckBoxSelectProps {
|
interface ICheckBoxSelectProps {
|
||||||
|
|
@ -510,7 +511,17 @@ export const CheckBoxSelect: React.FC<ICheckBoxSelectProps> = ({
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
const Option = (props: OptionProps<DisableOption, true>) => (
|
const Option = (props: OptionProps<DisableOption, true>) => (
|
||||||
<reactSelectComponents.Option {...props}>
|
<reactSelectComponents.Option
|
||||||
|
{...props}
|
||||||
|
className={`${props.className || ""} ${props.data.className || ""}`}
|
||||||
|
// data values don't seem to be included in props.innerProps by default
|
||||||
|
innerProps={
|
||||||
|
{ "data-value": props.data.value } as React.DetailedHTMLProps<
|
||||||
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
>
|
||||||
|
}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
disabled={props.isDisabled}
|
disabled={props.isDisabled}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { StashId } from "src/core/generated-graphql";
|
import { StashId } from "src/core/generated-graphql";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { getStashboxBase } from "src/utils/stashbox";
|
import { getStashboxBase } from "src/utils/stashbox";
|
||||||
import { ExternalLink } from "./ExternalLink";
|
import { ExternalLink } from "./ExternalLink";
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ export const StashIDPill: React.FC<{
|
||||||
stashID: Pick<StashId, "endpoint" | "stash_id">;
|
stashID: Pick<StashId, "endpoint" | "stash_id">;
|
||||||
linkType: LinkType;
|
linkType: LinkType;
|
||||||
}> = ({ stashID, linkType }) => {
|
}> = ({ stashID, linkType }) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
const { endpoint, stash_id } = stashID;
|
const { endpoint, stash_id } = stashID;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import { ModalComponent } from "src/components/Shared/Modal";
|
||||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||||
import { ErrorMessage } from "src/components/Shared/ErrorMessage";
|
import { ErrorMessage } from "src/components/Shared/ErrorMessage";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { StudioScenesPanel } from "./StudioScenesPanel";
|
import { StudioScenesPanel } from "./StudioScenesPanel";
|
||||||
import { StudioGalleriesPanel } from "./StudioGalleriesPanel";
|
import { StudioGalleriesPanel } from "./StudioGalleriesPanel";
|
||||||
import { StudioImagesPanel } from "./StudioImagesPanel";
|
import { StudioImagesPanel } from "./StudioImagesPanel";
|
||||||
|
|
@ -264,7 +264,7 @@ const StudioPage: React.FC<IProps> = ({ studio, tabKey }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
// Configuration settings
|
// Configuration settings
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const uiConfig = configuration?.ui;
|
const uiConfig = configuration?.ui;
|
||||||
const abbreviateCounter = uiConfig?.abbreviateCounters ?? false;
|
const abbreviateCounter = uiConfig?.abbreviateCounters ?? false;
|
||||||
const enableBackgroundImage = uiConfig?.enableStudioBackgroundImage ?? false;
|
const enableBackgroundImage = uiConfig?.enableStudioBackgroundImage ?? false;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
queryFindStudiosByIDForSelect,
|
queryFindStudiosByIDForSelect,
|
||||||
queryFindStudiosForSelect,
|
queryFindStudiosForSelect,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { defaultMaxOptionsShown, IUIConfig } from "src/core/config";
|
import { defaultMaxOptionsShown, IUIConfig } from "src/core/config";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
|
@ -65,7 +65,7 @@ const _StudioSelect: React.FC<
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const [createStudio] = useStudioCreate();
|
const [createStudio] = useStudioCreate();
|
||||||
|
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const maxOptionsShown =
|
const maxOptionsShown =
|
||||||
(configuration?.ui as IUIConfig).maxOptionsShown ?? defaultMaxOptionsShown;
|
(configuration?.ui as IUIConfig).maxOptionsShown ?? defaultMaxOptionsShown;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { useCallback, useContext } from "react";
|
import { useCallback } from "react";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { initialConfig, ITaggerConfig } from "./constants";
|
import { initialConfig, ITaggerConfig } from "./constants";
|
||||||
import { useConfigureUISetting } from "src/core/StashService";
|
import { useConfigureUISetting } from "src/core/StashService";
|
||||||
|
|
||||||
export function useTaggerConfig() {
|
export function useTaggerConfig() {
|
||||||
const { configuration: stashConfig } = useContext(ConfigurationContext);
|
const { configuration: stashConfig } = useConfigurationContext();
|
||||||
const [saveUISetting] = useConfigureUISetting();
|
const [saveUISetting] = useConfigureUISetting();
|
||||||
|
|
||||||
const config = stashConfig?.ui.taggerConfig ?? initialConfig;
|
const config = stashConfig?.ui.taggerConfig ?? initialConfig;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
useTagCreate,
|
useTagCreate,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { ITaggerSource, SCRAPER_PREFIX, STASH_BOX_PREFIX } from "./constants";
|
import { ITaggerSource, SCRAPER_PREFIX, STASH_BOX_PREFIX } from "./constants";
|
||||||
import { errorToString } from "src/utils";
|
import { errorToString } from "src/utils";
|
||||||
import { mergeStudioStashIDs } from "./utils";
|
import { mergeStudioStashIDs } from "./utils";
|
||||||
|
|
@ -117,7 +117,7 @@ export const TaggerContext: React.FC = ({ children }) => {
|
||||||
|
|
||||||
const stopping = useRef(false);
|
const stopping = useRef(false);
|
||||||
|
|
||||||
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
const { configuration: stashConfig } = useConfigurationContext();
|
||||||
const { config, setConfig } = useTaggerConfig();
|
const { config, setConfig } = useTaggerConfig();
|
||||||
|
|
||||||
const Scrapers = useListSceneScrapers();
|
const Scrapers = useListSceneScrapers();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { Dispatch, useState } from "react";
|
import React, { Dispatch, useState } from "react";
|
||||||
import { Badge, Button, Card, Collapse, Form } from "react-bootstrap";
|
import { Badge, Button, Card, Collapse, Form } from "react-bootstrap";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
import { ITaggerConfig } from "../constants";
|
import { ITaggerConfig } from "../constants";
|
||||||
import PerformerFieldSelector from "../PerformerFieldSelector";
|
import PerformerFieldSelector from "../PerformerFieldSelector";
|
||||||
|
|
@ -13,7 +13,7 @@ interface IConfigProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
||||||
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
const { configuration: stashConfig } = useConfigurationContext();
|
||||||
const [showExclusionModal, setShowExclusionModal] = useState(false);
|
const [showExclusionModal, setShowExclusionModal] = useState(false);
|
||||||
|
|
||||||
const excludedFields = config.excludedPerformerFields ?? [];
|
const excludedFields = config.excludedPerformerFields ?? [];
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
performerMutationImpactedQueries,
|
performerMutationImpactedQueries,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { Manual } from "src/components/Help/Manual";
|
import { Manual } from "src/components/Help/Manual";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
import StashSearchResult from "./StashSearchResult";
|
import StashSearchResult from "./StashSearchResult";
|
||||||
import PerformerConfig from "./Config";
|
import PerformerConfig from "./Config";
|
||||||
|
|
@ -620,7 +620,7 @@ interface ITaggerProps {
|
||||||
export const PerformerTagger: React.FC<ITaggerProps> = ({ performers }) => {
|
export const PerformerTagger: React.FC<ITaggerProps> = ({ performers }) => {
|
||||||
const jobsSubscribe = useJobsSubscribe();
|
const jobsSubscribe = useJobsSubscribe();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
const { configuration: stashConfig } = useConfigurationContext();
|
||||||
const { config, setConfig } = useTaggerConfig();
|
const { config, setConfig } = useTaggerConfig();
|
||||||
const [showConfig, setShowConfig] = useState(false);
|
const [showConfig, setShowConfig] = useState(false);
|
||||||
const [showManual, setShowManual] = useState(false);
|
const [showManual, setShowManual] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import Config from "./Config";
|
||||||
import { TaggerScene } from "./TaggerScene";
|
import { TaggerScene } from "./TaggerScene";
|
||||||
import { SceneTaggerModals } from "./sceneTaggerModals";
|
import { SceneTaggerModals } from "./sceneTaggerModals";
|
||||||
import { SceneSearchResults } from "./StashSearchResult";
|
import { SceneSearchResults } from "./StashSearchResult";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { faCog } from "@fortawesome/free-solid-svg-icons";
|
import { faCog } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useLightbox } from "src/hooks/Lightbox/hooks";
|
import { useLightbox } from "src/hooks/Lightbox/hooks";
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ const Scene: React.FC<{
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { currentSource, doSceneQuery, doSceneFragmentScrape, loading } =
|
const { currentSource, doSceneQuery, doSceneFragmentScrape, loading } =
|
||||||
useContext(TaggerStateContext);
|
useContext(TaggerStateContext);
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
|
|
||||||
const cont = configuration?.interface.continuePlaylistDefault ?? false;
|
const cont = configuration?.interface.continuePlaylistDefault ?? false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import {
|
||||||
faImage,
|
faImage,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { objectPath, objectTitle } from "src/core/files";
|
import { objectPath, objectTitle } from "src/core/files";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
|
|
||||||
interface ITaggerSceneDetails {
|
interface ITaggerSceneDetails {
|
||||||
|
|
@ -154,7 +154,7 @@ export const TaggerScene: React.FC<PropsWithChildren<ITaggerScene>> = ({
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const cont = configuration?.interface.continuePlaylistDefault ?? false;
|
const cont = configuration?.interface.continuePlaylistDefault ?? false;
|
||||||
|
|
||||||
async function query() {
|
async function query() {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { Dispatch, useState } from "react";
|
import React, { Dispatch, useState } from "react";
|
||||||
import { Badge, Button, Card, Collapse, Form } from "react-bootstrap";
|
import { Badge, Button, Card, Collapse, Form } from "react-bootstrap";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
import { ITaggerConfig } from "../constants";
|
import { ITaggerConfig } from "../constants";
|
||||||
import StudioFieldSelector from "./StudioFieldSelector";
|
import StudioFieldSelector from "./StudioFieldSelector";
|
||||||
|
|
@ -13,7 +13,7 @@ interface IConfigProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
||||||
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
const { configuration: stashConfig } = useConfigurationContext();
|
||||||
const [showExclusionModal, setShowExclusionModal] = useState(false);
|
const [showExclusionModal, setShowExclusionModal] = useState(false);
|
||||||
|
|
||||||
const excludedFields = config.excludedStudioFields ?? [];
|
const excludedFields = config.excludedStudioFields ?? [];
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
evictQueries,
|
evictQueries,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { Manual } from "src/components/Help/Manual";
|
import { Manual } from "src/components/Help/Manual";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
import StashSearchResult from "./StashSearchResult";
|
import StashSearchResult from "./StashSearchResult";
|
||||||
import StudioConfig from "./Config";
|
import StudioConfig from "./Config";
|
||||||
|
|
@ -669,7 +669,7 @@ interface ITaggerProps {
|
||||||
export const StudioTagger: React.FC<ITaggerProps> = ({ studios }) => {
|
export const StudioTagger: React.FC<ITaggerProps> = ({ studios }) => {
|
||||||
const jobsSubscribe = useJobsSubscribe();
|
const jobsSubscribe = useJobsSubscribe();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
const { configuration: stashConfig } = useConfigurationContext();
|
||||||
const { config, setConfig } = useTaggerConfig();
|
const { config, setConfig } = useTaggerConfig();
|
||||||
const [showConfig, setShowConfig] = useState(false);
|
const [showConfig, setShowConfig] = useState(false);
|
||||||
const [showManual, setShowManual] = useState(false);
|
const [showManual, setShowManual] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import { ModalComponent } from "src/components/Shared/Modal";
|
||||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { tagRelationHook } from "src/core/tags";
|
import { tagRelationHook } from "src/core/tags";
|
||||||
import { TagScenesPanel } from "./TagScenesPanel";
|
import { TagScenesPanel } from "./TagScenesPanel";
|
||||||
import { TagMarkersPanel } from "./TagMarkersPanel";
|
import { TagMarkersPanel } from "./TagMarkersPanel";
|
||||||
|
|
@ -293,7 +293,7 @@ const TagPage: React.FC<IProps> = ({ tag, tabKey }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
// Configuration settings
|
// Configuration settings
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const uiConfig = configuration?.ui;
|
const uiConfig = configuration?.ui;
|
||||||
const abbreviateCounter = uiConfig?.abbreviateCounters ?? false;
|
const abbreviateCounter = uiConfig?.abbreviateCounters ?? false;
|
||||||
const enableBackgroundImage = uiConfig?.enableTagBackgroundImage ?? false;
|
const enableBackgroundImage = uiConfig?.enableTagBackgroundImage ?? false;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { useFindTag } from "../../core/StashService";
|
import { useFindTag } from "../../core/StashService";
|
||||||
import { TagCard } from "./TagCard";
|
import { TagCard } from "./TagCard";
|
||||||
import { ConfigurationContext } from "../../hooks/Config";
|
import { useConfigurationContext } from "../../hooks/Config";
|
||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
|
|
||||||
interface ITagPopoverCardProps {
|
interface ITagPopoverCardProps {
|
||||||
|
|
@ -47,7 +47,7 @@ export const TagPopover: React.FC<ITagPopoverProps> = ({
|
||||||
placement = "top",
|
placement = "top",
|
||||||
target,
|
target,
|
||||||
}) => {
|
}) => {
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
|
|
||||||
const showTagCardOnHover = config?.ui.showTagCardOnHover ?? true;
|
const showTagCardOnHover = config?.ui.showTagCardOnHover ?? true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
queryFindTagsByIDForSelect,
|
queryFindTagsByIDForSelect,
|
||||||
queryFindTagsForSelect,
|
queryFindTagsForSelect,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { defaultMaxOptionsShown } from "src/core/config";
|
import { defaultMaxOptionsShown } from "src/core/config";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
|
@ -67,7 +67,7 @@ export type TagSelectProps = IFilterProps &
|
||||||
const _TagSelect: React.FC<TagSelectProps> = (props) => {
|
const _TagSelect: React.FC<TagSelectProps> = (props) => {
|
||||||
const [createTag] = useTagCreate();
|
const [createTag] = useTagCreate();
|
||||||
|
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const maxOptionsShown =
|
const maxOptionsShown =
|
||||||
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import TextUtils from "src/utils/text";
|
||||||
import NavUtils from "src/utils/navigation";
|
import NavUtils from "src/utils/navigation";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { markerTitle } from "src/core/markers";
|
import { markerTitle } from "src/core/markers";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
|
|
||||||
|
|
@ -128,7 +128,7 @@ export const WallItem = <T extends WallItemType>({
|
||||||
}: IWallItemProps<T>) => {
|
}: IWallItemProps<T>) => {
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
const itemEl = useRef<HTMLDivElement>(null);
|
const itemEl = useRef<HTMLDivElement>(null);
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
|
|
||||||
const showTextContainer = config?.interface.wallShowTitle ?? true;
|
const showTextContainer = config?.interface.wallShowTitle ?? true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,15 @@
|
||||||
|
|
||||||
Setting the language affects the formatting of numbers and dates.
|
Setting the language affects the formatting of numbers and dates.
|
||||||
|
|
||||||
|
## SFW Content Mode
|
||||||
|
|
||||||
|
SFW Content Mode is used to indicate that the content being managed is _not_ adult content.
|
||||||
|
|
||||||
|
When SFW Content Mode is enabled, the following changes are made to the UI:
|
||||||
|
- default performer images are changed to less adult-oriented images
|
||||||
|
- certain adult-specific metadata fields are hidden (e.g. performer genital fields)
|
||||||
|
- `O`-Counter is replaced with `Like`-counter
|
||||||
|
|
||||||
## Scene/Marker Wall Preview Type
|
## Scene/Marker Wall Preview Type
|
||||||
|
|
||||||
The Scene Wall and Marker pages display scene preview videos (mp4) by default. This can be changed to animated image (webp) or static image.
|
The Scene Wall and Marker pages display scene preview videos (mp4) by default. This can be changed to animated image (webp) or static image.
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,28 @@ import React from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
|
||||||
export interface IContext {
|
export interface IContext {
|
||||||
configuration?: GQL.ConfigDataFragment;
|
configuration: GQL.ConfigDataFragment;
|
||||||
loading?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConfigurationContext = React.createContext<IContext>({});
|
export const ConfigurationContext = React.createContext<IContext | null>(null);
|
||||||
|
|
||||||
|
export const useConfigurationContext = () => {
|
||||||
|
const context = React.useContext(ConfigurationContext);
|
||||||
|
|
||||||
|
if (context === null) {
|
||||||
|
throw new Error(
|
||||||
|
"useConfigurationContext must be used within a ConfigurationProvider"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useConfigurationContextOptional = () => {
|
||||||
|
return React.useContext(ConfigurationContext);
|
||||||
|
};
|
||||||
|
|
||||||
export const ConfigurationProvider: React.FC<IContext> = ({
|
export const ConfigurationProvider: React.FC<IContext> = ({
|
||||||
loading,
|
|
||||||
configuration,
|
configuration,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
@ -17,7 +31,6 @@ export const ConfigurationProvider: React.FC<IContext> = ({
|
||||||
<ConfigurationContext.Provider
|
<ConfigurationContext.Provider
|
||||||
value={{
|
value={{
|
||||||
configuration,
|
configuration,
|
||||||
loading,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useCallback, useContext, useEffect, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useState } from "react";
|
||||||
import { ConfigurationContext } from "../Config";
|
import { useConfigurationContext } from "../Config";
|
||||||
import { useLocalForage } from "../LocalForage";
|
import { useLocalForage } from "../LocalForage";
|
||||||
import { Interactive as InteractiveAPI } from "./interactive";
|
import { Interactive as InteractiveAPI } from "./interactive";
|
||||||
import InteractiveUtils, {
|
import InteractiveUtils, {
|
||||||
|
|
@ -86,7 +86,7 @@ export const InteractiveProvider: React.FC = ({ children }) => {
|
||||||
{ serverOffset: 0, lastSyncTime: 0 }
|
{ serverOffset: 0, lastSyncTime: 0 }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
const { configuration: stashConfig } = useConfigurationContext();
|
||||||
|
|
||||||
const [state, setState] = useState<ConnectionState>(ConnectionState.Missing);
|
const [state, setState] = useState<ConnectionState>(ConnectionState.Missing);
|
||||||
const [handyKey, setHandyKey] = useState<string | undefined>(undefined);
|
const [handyKey, setHandyKey] = useState<string | undefined>(undefined);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import usePageVisibility from "../PageVisibility";
|
||||||
import { useToast } from "../Toast";
|
import { useToast } from "../Toast";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { LightboxImage } from "./LightboxImage";
|
import { LightboxImage } from "./LightboxImage";
|
||||||
import { ConfigurationContext } from "../Config";
|
import { useConfigurationContext } from "../Config";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { OCounterButton } from "src/components/Scenes/SceneDetails/OCounterButton";
|
import { OCounterButton } from "src/components/Scenes/SceneDetails/OCounterButton";
|
||||||
import {
|
import {
|
||||||
|
|
@ -154,7 +154,7 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
|
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = useConfigurationContext();
|
||||||
const [interfaceLocalForage, setInterfaceLocalForage] =
|
const [interfaceLocalForage, setInterfaceLocalForage] =
|
||||||
useInterfaceLocalForage();
|
useInterfaceLocalForage();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { useContext } from "react";
|
|
||||||
import { useConfigureUI } from "src/core/StashService";
|
import { useConfigureUI } from "src/core/StashService";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { useToast } from "./Toast";
|
import { useToast } from "./Toast";
|
||||||
|
|
||||||
export const useTableColumns = (
|
export const useTableColumns = (
|
||||||
|
|
@ -9,7 +8,7 @@ export const useTableColumns = (
|
||||||
) => {
|
) => {
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
|
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useConfigurationContext();
|
||||||
const [saveUI] = useConfigureUI();
|
const [saveUI] = useConfigureUI();
|
||||||
|
|
||||||
const ui = configuration?.ui;
|
const ui = configuration?.ui;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ $sidebar-width: 250px;
|
||||||
@import "styles/theme";
|
@import "styles/theme";
|
||||||
@import "styles/range";
|
@import "styles/range";
|
||||||
@import "styles/scrollbars";
|
@import "styles/scrollbars";
|
||||||
|
@import "sfw-mode.scss";
|
||||||
@import "src/components/Changelog/styles.scss";
|
@import "src/components/Changelog/styles.scss";
|
||||||
@import "src/components/Galleries/styles.scss";
|
@import "src/components/Galleries/styles.scss";
|
||||||
@import "src/components/Help/styles.scss";
|
@import "src/components/Help/styles.scss";
|
||||||
|
|
|
||||||
|
|
@ -784,6 +784,10 @@
|
||||||
"description": "Number of times to attempt to scroll before moving to the next/previous item. Only applies for Pan Y scroll mode.",
|
"description": "Number of times to attempt to scroll before moving to the next/previous item. Only applies for Pan Y scroll mode.",
|
||||||
"heading": "Scroll attempts before transition"
|
"heading": "Scroll attempts before transition"
|
||||||
},
|
},
|
||||||
|
"sfw_mode": {
|
||||||
|
"description": "Enable if using stash to store SFW content. Hides or changes some adult-content-related aspects of the UI.",
|
||||||
|
"heading": "SFW Content Mode"
|
||||||
|
},
|
||||||
"show_tag_card_on_hover": {
|
"show_tag_card_on_hover": {
|
||||||
"description": "Show tag card when hovering tag badges",
|
"description": "Show tag card when hovering tag badges",
|
||||||
"heading": "Tag card tooltips"
|
"heading": "Tag card tooltips"
|
||||||
|
|
@ -897,6 +901,7 @@
|
||||||
"developmentVersion": "Development Version",
|
"developmentVersion": "Development Version",
|
||||||
"dialogs": {
|
"dialogs": {
|
||||||
"clear_o_history_confirm": "Are you sure you want to clear the O history?",
|
"clear_o_history_confirm": "Are you sure you want to clear the O history?",
|
||||||
|
"clear_o_history_confirm_sfw": "Are you sure you want to clear the like history?",
|
||||||
"clear_play_history_confirm": "Are you sure you want to clear the play history?",
|
"clear_play_history_confirm": "Are you sure you want to clear the play history?",
|
||||||
"create_new_entity": "Create new {entity}",
|
"create_new_entity": "Create new {entity}",
|
||||||
"delete_alert": "The following {count, plural, one {{singularEntity}} other {{pluralEntity}}} will be deleted permanently:",
|
"delete_alert": "The following {count, plural, one {{singularEntity}} other {{pluralEntity}}} will be deleted permanently:",
|
||||||
|
|
@ -1158,6 +1163,7 @@
|
||||||
"interactive_speed": "Interactive Speed",
|
"interactive_speed": "Interactive Speed",
|
||||||
"isMissing": "Is Missing",
|
"isMissing": "Is Missing",
|
||||||
"last_o_at": "Last O At",
|
"last_o_at": "Last O At",
|
||||||
|
"last_o_at_sfw": "Last Like At",
|
||||||
"last_played_at": "Last Played At",
|
"last_played_at": "Last Played At",
|
||||||
"library": "Library",
|
"library": "Library",
|
||||||
"loading": {
|
"loading": {
|
||||||
|
|
@ -1198,9 +1204,11 @@
|
||||||
"new": "New",
|
"new": "New",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"o_count": "O Count",
|
"o_count": "O Count",
|
||||||
"o_counter": "O-Counter",
|
"o_count_sfw": "Likes",
|
||||||
"o_history": "O History",
|
"o_history": "O History",
|
||||||
|
"o_history_sfw": "Like History",
|
||||||
"odate_recorded_no": "No O Date Recorded",
|
"odate_recorded_no": "No O Date Recorded",
|
||||||
|
"odate_recorded_no_sfw": "No Like Date Recorded",
|
||||||
"operations": "Operations",
|
"operations": "Operations",
|
||||||
"organized": "Organised",
|
"organized": "Organised",
|
||||||
"orientation": "Orientation",
|
"orientation": "Orientation",
|
||||||
|
|
@ -1377,25 +1385,28 @@
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"database_filename_empty_for_default": "database filename (empty for default)",
|
"database_filename_empty_for_default": "database filename (empty for default)",
|
||||||
"description": "Next up, we need to determine where to find your porn collection, and where to store the Stash database, generated files and cache files. These settings can be changed later if needed.",
|
"description": "Next up, we need to determine where to find your content, and where to store the Stash database, generated files and cache files. These settings can be changed later if needed.",
|
||||||
"path_to_blobs_directory_empty_for_default": "path to blobs directory (empty for default)",
|
"path_to_blobs_directory_empty_for_default": "path to blobs directory (empty for default)",
|
||||||
"path_to_cache_directory_empty_for_default": "path to cache directory (empty for default)",
|
"path_to_cache_directory_empty_for_default": "path to cache directory (empty for default)",
|
||||||
"path_to_generated_directory_empty_for_default": "path to generated directory (empty for default)",
|
"path_to_generated_directory_empty_for_default": "path to generated directory (empty for default)",
|
||||||
"set_up_your_paths": "Set up your paths",
|
"set_up_your_paths": "Set up your paths",
|
||||||
|
"sfw_content_settings": "Using stash for SFW content?",
|
||||||
|
"sfw_content_settings_description": "stash can be used to manage SFW content such as photography, art, comics, and more. Enabling this option will adjust some UI behaviour to be more appropriate for SFW content.",
|
||||||
"stash_alert": "No library paths have been selected. No media will be able to be scanned into Stash. Are you sure?",
|
"stash_alert": "No library paths have been selected. No media will be able to be scanned into Stash. Are you sure?",
|
||||||
"store_blobs_in_database": "Store blobs in database",
|
"store_blobs_in_database": "Store blobs in database",
|
||||||
|
"use_sfw_content_mode": "Use SFW content mode",
|
||||||
"where_can_stash_store_blobs": "Where can Stash store database binary data?",
|
"where_can_stash_store_blobs": "Where can Stash store database binary data?",
|
||||||
"where_can_stash_store_blobs_description": "Stash can store binary data such as scene covers, performer, studio and tag images either in the database or in the filesystem. By default, it will store this data in the filesystem in the subdirectory <code>blobs</code> within the directory containing your config file. If you want to change this, please enter an absolute or relative (to the current working directory) path. Stash will create this directory if it does not already exist.",
|
"where_can_stash_store_blobs_description": "Stash can store binary data such as scene covers, performer, studio and tag images either in the database or in the filesystem. By default, it will store this data in the filesystem in the subdirectory <code>blobs</code> within the directory containing your config file. If you want to change this, please enter an absolute or relative (to the current working directory) path. Stash will create this directory if it does not already exist.",
|
||||||
"where_can_stash_store_blobs_description_addendum": "Alternatively, you can store this data in the database. <strong>Note:</strong> This will increase the size of your database file, and will increase database migration times.",
|
"where_can_stash_store_blobs_description_addendum": "Alternatively, you can store this data in the database. <strong>Note:</strong> This will increase the size of your database file, and will increase database migration times.",
|
||||||
"where_can_stash_store_cache_files": "Where can Stash store cache files?",
|
"where_can_stash_store_cache_files": "Where can Stash store cache files?",
|
||||||
"where_can_stash_store_cache_files_description": "In order for some functionality like HLS/DASH live transcoding to function, Stash requires a cache directory for temporary files. By default, Stash will create a <code>cache</code> directory within the directory containing your config file. If you want to change this, please enter an absolute or relative (to the current working directory) path. Stash will create this directory if it does not already exist.",
|
"where_can_stash_store_cache_files_description": "In order for some functionality like HLS/DASH live transcoding to function, Stash requires a cache directory for temporary files. By default, Stash will create a <code>cache</code> directory within the directory containing your config file. If you want to change this, please enter an absolute or relative (to the current working directory) path. Stash will create this directory if it does not already exist.",
|
||||||
"where_can_stash_store_its_database": "Where can Stash store its database?",
|
"where_can_stash_store_its_database": "Where can Stash store its database?",
|
||||||
"where_can_stash_store_its_database_description": "Stash uses an SQLite database to store your porn metadata. By default, this will be created as <code>stash-go.sqlite</code> in the directory containing your config file. If you want to change this, please enter an absolute or relative (to the current working directory) filename.",
|
"where_can_stash_store_its_database_description": "Stash uses an SQLite database to store your content metadata. By default, this will be created as <code>stash-go.sqlite</code> in the directory containing your config file. If you want to change this, please enter an absolute or relative (to the current working directory) filename.",
|
||||||
"where_can_stash_store_its_database_warning": "WARNING: storing the database on a different system to where Stash is run from (e.g. storing the database on a NAS while running the Stash server on another computer) is <strong>unsupported</strong>! SQLite is not intended for use across a network, and attempting to do so can very easily cause your entire database to become corrupted.",
|
"where_can_stash_store_its_database_warning": "WARNING: storing the database on a different system to where Stash is run from (e.g. storing the database on a NAS while running the Stash server on another computer) is <strong>unsupported</strong>! SQLite is not intended for use across a network, and attempting to do so can very easily cause your entire database to become corrupted.",
|
||||||
"where_can_stash_store_its_generated_content": "Where can Stash store its generated content?",
|
"where_can_stash_store_its_generated_content": "Where can Stash store its generated content?",
|
||||||
"where_can_stash_store_its_generated_content_description": "In order to provide thumbnails, previews and sprites, Stash generates images and videos. This also includes transcodes for unsupported file formats. By default, Stash will create a <code>generated</code> directory within the directory containing your config file. If you want to change where this generated media will be stored, please enter an absolute or relative (to the current working directory) path. Stash will create this directory if it does not already exist.",
|
"where_can_stash_store_its_generated_content_description": "In order to provide thumbnails, previews and sprites, Stash generates images and videos. This also includes transcodes for unsupported file formats. By default, Stash will create a <code>generated</code> directory within the directory containing your config file. If you want to change where this generated media will be stored, please enter an absolute or relative (to the current working directory) path. Stash will create this directory if it does not already exist.",
|
||||||
"where_is_your_porn_located": "Where is your porn located?",
|
"where_is_your_porn_located": "Where is your content located?",
|
||||||
"where_is_your_porn_located_description": "Add directories containing your porn videos and images. Stash will use these directories to find videos and images during scanning."
|
"where_is_your_porn_located_description": "Add directories containing your videos and images. Stash will use these directories to find videos and images during scanning."
|
||||||
},
|
},
|
||||||
"stash_setup_wizard": "Stash Setup Wizard",
|
"stash_setup_wizard": "Stash Setup Wizard",
|
||||||
"success": {
|
"success": {
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ export abstract class Criterion {
|
||||||
|
|
||||||
protected cloneValues() {}
|
protected cloneValues() {}
|
||||||
|
|
||||||
public abstract getLabel(intl: IntlShape): string;
|
public abstract getLabel(intl: IntlShape, sfwContentMode?: boolean): string;
|
||||||
|
|
||||||
public getId(): string {
|
public getId(): string {
|
||||||
return `${this.criterionOption.type}`;
|
return `${this.criterionOption.type}`;
|
||||||
|
|
@ -148,7 +148,7 @@ export abstract class ModifierCriterion<
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabel(intl: IntlShape): string {
|
public getLabel(intl: IntlShape, sfwContentMode: boolean = false): string {
|
||||||
const modifierString = ModifierCriterion.getModifierLabel(
|
const modifierString = ModifierCriterion.getModifierLabel(
|
||||||
intl,
|
intl,
|
||||||
this.modifier
|
this.modifier
|
||||||
|
|
@ -162,10 +162,14 @@ export abstract class ModifierCriterion<
|
||||||
valueString = this.getLabelValue(intl);
|
valueString = this.getLabelValue(intl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messageID = !sfwContentMode
|
||||||
|
? this.criterionOption.messageID
|
||||||
|
: this.criterionOption.sfwMessageID ?? this.criterionOption.messageID;
|
||||||
|
|
||||||
return intl.formatMessage(
|
return intl.formatMessage(
|
||||||
{ id: "criterion_modifier.format_string" },
|
{ id: "criterion_modifier.format_string" },
|
||||||
{
|
{
|
||||||
criterion: intl.formatMessage({ id: this.criterionOption.messageID }),
|
criterion: intl.formatMessage({ id: messageID }),
|
||||||
modifierString,
|
modifierString,
|
||||||
valueString,
|
valueString,
|
||||||
}
|
}
|
||||||
|
|
@ -257,12 +261,14 @@ interface ICriterionOptionParams {
|
||||||
type: CriterionType;
|
type: CriterionType;
|
||||||
makeCriterion: MakeCriterionFn;
|
makeCriterion: MakeCriterionFn;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
|
sfwMessageID?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CriterionOption {
|
export class CriterionOption {
|
||||||
public readonly type: CriterionType;
|
public readonly type: CriterionType;
|
||||||
public readonly messageID: string;
|
public readonly messageID: string;
|
||||||
public readonly makeCriterionFn: MakeCriterionFn;
|
public readonly makeCriterionFn: MakeCriterionFn;
|
||||||
|
public readonly sfwMessageID?: string;
|
||||||
|
|
||||||
// used for legacy criteria that are not shown in the UI
|
// used for legacy criteria that are not shown in the UI
|
||||||
public readonly hidden: boolean = false;
|
public readonly hidden: boolean = false;
|
||||||
|
|
@ -272,6 +278,7 @@ export class CriterionOption {
|
||||||
this.messageID = options.messageID;
|
this.messageID = options.messageID;
|
||||||
this.makeCriterionFn = options.makeCriterion;
|
this.makeCriterionFn = options.makeCriterion;
|
||||||
this.hidden = options.hidden ?? false;
|
this.hidden = options.hidden ?? false;
|
||||||
|
this.sfwMessageID = options.sfwMessageID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public makeCriterion(config?: ConfigDataFragment) {
|
public makeCriterion(config?: ConfigDataFragment) {
|
||||||
|
|
@ -478,7 +485,7 @@ export class IHierarchicalLabeledIdCriterion extends ModifierCriterion<IHierarch
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabel(intl: IntlShape): string {
|
public getLabel(intl: IntlShape, sfwContentMode?: boolean): string {
|
||||||
let id = "criterion_modifier.format_string";
|
let id = "criterion_modifier.format_string";
|
||||||
let modifierString = ModifierCriterion.getModifierLabel(
|
let modifierString = ModifierCriterion.getModifierLabel(
|
||||||
intl,
|
intl,
|
||||||
|
|
@ -511,10 +518,14 @@ export class IHierarchicalLabeledIdCriterion extends ModifierCriterion<IHierarch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messageID = !sfwContentMode
|
||||||
|
? this.criterionOption.messageID
|
||||||
|
: this.criterionOption.sfwMessageID ?? this.criterionOption.messageID;
|
||||||
|
|
||||||
return intl.formatMessage(
|
return intl.formatMessage(
|
||||||
{ id },
|
{ id },
|
||||||
{
|
{
|
||||||
criterion: intl.formatMessage({ id: this.criterionOption.messageID }),
|
criterion: intl.formatMessage({ id: messageID }),
|
||||||
modifierString,
|
modifierString,
|
||||||
valueString,
|
valueString,
|
||||||
excludedString,
|
excludedString,
|
||||||
|
|
@ -552,9 +563,14 @@ export class StringCriterionOption extends ModifierCriterionOption {
|
||||||
|
|
||||||
export function createStringCriterionOption(
|
export function createStringCriterionOption(
|
||||||
type: CriterionType,
|
type: CriterionType,
|
||||||
messageID?: string
|
messageID?: string,
|
||||||
|
options?: { nsfw?: boolean }
|
||||||
) {
|
) {
|
||||||
return new StringCriterionOption({ messageID: messageID ?? type, type });
|
return new StringCriterionOption({
|
||||||
|
messageID: messageID ?? type,
|
||||||
|
type,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MandatoryStringCriterionOption extends ModifierCriterionOption {
|
export class MandatoryStringCriterionOption extends ModifierCriterionOption {
|
||||||
|
|
@ -755,7 +771,8 @@ export class MandatoryNumberCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(
|
constructor(
|
||||||
messageID: string,
|
messageID: string,
|
||||||
value: CriterionType,
|
value: CriterionType,
|
||||||
makeCriterion?: () => ModifierCriterion<CriterionValue>
|
makeCriterion?: () => ModifierCriterion<CriterionValue>,
|
||||||
|
options?: { sfwMessageID?: string }
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
|
|
@ -773,15 +790,22 @@ export class MandatoryNumberCriterionOption extends ModifierCriterionOption {
|
||||||
makeCriterion: makeCriterion
|
makeCriterion: makeCriterion
|
||||||
? makeCriterion
|
? makeCriterion
|
||||||
: () => new NumberCriterion(this),
|
: () => new NumberCriterion(this),
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMandatoryNumberCriterionOption(
|
export function createMandatoryNumberCriterionOption(
|
||||||
value: CriterionType,
|
value: CriterionType,
|
||||||
messageID?: string
|
messageID?: string,
|
||||||
|
options?: { sfwMessageID?: string }
|
||||||
) {
|
) {
|
||||||
return new MandatoryNumberCriterionOption(messageID ?? value, value);
|
return new MandatoryNumberCriterionOption(
|
||||||
|
messageID ?? value,
|
||||||
|
value,
|
||||||
|
undefined,
|
||||||
|
options
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeRangeValue<V>(
|
export function encodeRangeValue<V>(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { DisplayMode } from "./types";
|
||||||
export interface ISortByOption {
|
export interface ISortByOption {
|
||||||
messageID: string;
|
messageID: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
sfwMessageID?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MediaSortByOptions = [
|
export const MediaSortByOptions = [
|
||||||
|
|
@ -22,7 +23,7 @@ export class ListFilterOptions {
|
||||||
public readonly displayModeOptions: DisplayMode[] = [];
|
public readonly displayModeOptions: DisplayMode[] = [];
|
||||||
public readonly criterionOptions: CriterionOption[] = [];
|
public readonly criterionOptions: CriterionOption[] = [];
|
||||||
|
|
||||||
public static createSortBy(value: string) {
|
public static createSortBy(value: string): ISortByOption {
|
||||||
return {
|
return {
|
||||||
messageID: value,
|
messageID: value,
|
||||||
value,
|
value,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ const sortByOptions = [
|
||||||
{
|
{
|
||||||
messageID: "o_count",
|
messageID: "o_count",
|
||||||
value: "o_counter",
|
value: "o_counter",
|
||||||
|
sfwMessageID: "o_count_sfw",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const displayModeOptions = [DisplayMode.Grid];
|
const displayModeOptions = [DisplayMode.Grid];
|
||||||
|
|
@ -53,7 +54,9 @@ const criterionOptions = [
|
||||||
RatingCriterionOption,
|
RatingCriterionOption,
|
||||||
PerformersCriterionOption,
|
PerformersCriterionOption,
|
||||||
createDateCriterionOption("date"),
|
createDateCriterionOption("date"),
|
||||||
createMandatoryNumberCriterionOption("o_counter", "o_count"),
|
createMandatoryNumberCriterionOption("o_counter", "o_count", {
|
||||||
|
sfwMessageID: "o_count_sfw",
|
||||||
|
}),
|
||||||
ContainingGroupsCriterionOption,
|
ContainingGroupsCriterionOption,
|
||||||
SubGroupsCriterionOption,
|
SubGroupsCriterionOption,
|
||||||
createMandatoryNumberCriterionOption("containing_group_count"),
|
createMandatoryNumberCriterionOption("containing_group_count"),
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue