Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/stashapp/stash?logo=github)](https://github.com/stashapp/stash/releases/latest)
[![GitHub issues by-label](https://img.shields.io/github/issues-raw/stashapp/stash/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.**
![demo image](docs/readme_assets/demo_image.png)

* 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.
Expand Down
8 changes: 8 additions & 0 deletions graphql/schema/types/config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ input SetupInput {
"Empty to indicate $HOME/.stash/config.yml default"
configLocation: String!
stashes: [StashConfigInput!]!
"True if SFW content mode is enabled"
sfwContentMode: Boolean
"Empty to indicate default"
databaseFile: String!
"Empty to indicate default"
Expand Down Expand Up @@ -341,6 +343,9 @@ type ConfigImageLightboxResult {
}

input ConfigInterfaceInput {
"True if SFW content mode is enabled"
sfwContentMode: Boolean

"Ordered list of items that should be shown in the menu"
menuItems: [String!]

Expand Down Expand Up @@ -407,6 +412,9 @@ type ConfigDisableDropdownCreate {
}

type ConfigInterfaceResult {
"True if SFW content mode is enabled"
sfwContentMode: Boolean!

"Ordered list of items that should be shown in the menu"
menuItems: [String!]

Expand Down
6 changes: 5 additions & 1 deletion internal/api/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
if performerBoxCustom != nil {
ret, err := performerBoxCustom.GetRandomImageByName(name)
Expand All @@ -111,6 +111,10 @@ func getDefaultPerformerImage(name string, gender *models.GenderEnum) []byte {
logger.Warnf("error loading custom default performer image: %v", err)
}

if sfwMode {
return static.ReadAll(static.DefaultSFWPerformerImage)
}

var g models.GenderEnum
if gender != nil {
g = *gender
Expand Down
2 changes: 2 additions & 0 deletions internal/api/resolver_mutation_configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigInterfaceInput) (*ConfigInterfaceResult, error) {
c := config.GetInstance()

r.setConfigBool(config.SFWContentMode, input.SfwContentMode)

if input.MenuItems != nil {
c.SetInterface(config.MenuItems, input.MenuItems)
}
Expand Down
1 change: 1 addition & 0 deletions internal/api/resolver_query_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
disableDropdownCreate := config.GetDisableDropdownCreate()

return &ConfigInterfaceResult{
SfwContentMode: config.GetSFWContentMode(),
MenuItems: menuItems,
SoundOnPreview: &soundOnPreview,
WallShowTitle: &wallShowTitle,
Expand Down
7 changes: 6 additions & 1 deletion internal/api/routes_performer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ type PerformerFinder interface {
GetImage(ctx context.Context, performerID int) ([]byte, error)
}

type sfwConfig interface {
GetSFWContentMode() bool
}

type performerRoutes struct {
routes
performerFinder PerformerFinder
sfwConfig sfwConfig
}

func (rs performerRoutes) Routes() chi.Router {
Expand Down Expand Up @@ -54,7 +59,7 @@ func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) {
}

if len(image) == 0 {
image = getDefaultPerformerImage(performer.Name, performer.Gender)
image = getDefaultPerformerImage(performer.Name, performer.Gender, rs.sfwConfig.GetSFWContentMode())
}

utils.ServeImage(w, r, image)
Expand Down
1 change: 1 addition & 0 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ func (s *Server) getPerformerRoutes() chi.Router {
return performerRoutes{
routes: routes{txnManager: repo.TxnManager},
performerFinder: repo.Performer,
sfwConfig: s.manager.Config,
}.Routes()
}

Expand Down
13 changes: 12 additions & 1 deletion internal/manager/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const (
Password = "password"
MaxSessionAge = "max_session_age"

// SFWContentMode mode config key
SFWContentMode = "sfw_content_mode"

FFMpegPath = "ffmpeg_path"
FFProbePath = "ffprobe_path"

Expand Down Expand Up @@ -628,7 +631,15 @@ func (i *Config) getStringMapString(key string) map[string]string {
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
// value only if the main value is not set.
func (i *Config) GetStashPaths() StashConfigs {
Expand Down
4 changes: 4 additions & 0 deletions internal/manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ func (s *Manager) Setup(ctx context.Context, input SetupInput) error {
cfg.SetString(config.Cache, input.CacheLocation)
}

if input.SFWContentMode {
cfg.SetBool(config.SFWContentMode, true)
}

if input.StoreBlobsInDatabase {
cfg.SetInterface(config.BlobsStorage, config.BlobStorageTypeDatabase)
} else {
Expand Down
1 change: 1 addition & 0 deletions internal/manager/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type SetupInput struct {
// Empty to indicate $HOME/.stash/config.yml default
ConfigLocation string `json:"configLocation"`
Stashes []*config.StashConfigInput `json:"stashes"`
SFWContentMode bool `json:"sfwContentMode"`
// Empty to indicate default
DatabaseFile string `json:"databaseFile"`
// Empty to indicate default
Expand Down
7 changes: 4 additions & 3 deletions internal/static/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import (
"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

const (
Performer = "performer"
PerformerMale = "performer_male"
Performer = "performer"
PerformerMale = "performer_male"
DefaultSFWPerformerImage = "performer_sfw/performer.svg"

Scene = "scene"
DefaultSceneImage = "scene/scene.svg"
Expand Down
7 changes: 7 additions & 0 deletions internal/static/performer_sfw/performer.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions ui/v2.5/graphql/data/config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
}

fragment ConfigInterfaceData on ConfigInterfaceResult {
sfwContentMode
menuItems
soundOnPreview
wallShowTitle
Expand Down
54 changes: 36 additions & 18 deletions ui/v2.5/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ import * as GQL from "./core/generated-graphql";
import { makeTitleProps } from "./hooks/title";
import { LoadingIndicator } from "./components/Shared/LoadingIndicator";

import { ConfigurationProvider } from "./hooks/Config";
import {
ConfigurationProvider,
useConfigurationContextOptional,
} from "./hooks/Config";
import { ManualProvider } from "./components/Help/context";
import { InteractiveProvider } from "./hooks/Interactive/context";
import { ReleaseNotesDialog } from "./components/Dialogs/ReleaseNotesDialog";
Expand All @@ -50,6 +53,7 @@ import { PatchFunction } from "./patch";

import moment from "moment/min/moment-with-locales";
import { ErrorMessage } from "./components/Shared/ErrorMessage";
import cx from "classnames";

const Performers = lazyComponent(
() => import("./components/Performers/Performers")
Expand Down Expand Up @@ -104,8 +108,17 @@ const AppContainer: React.FC<React.PropsWithChildren<{}>> = PatchFunction(
) as React.FC;

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 (
<div className={`main container-fluid ${appleRendering ? "apple" : ""}`}>
<div
className={cx("main container-fluid", {
apple: appleRendering,
"sfw-content-mode": sfwContentMode,
})}
>
{children}
</div>
);
Expand Down Expand Up @@ -300,28 +313,36 @@ export const App: React.FC = () => {
return null;
}

if (config.error) {
function renderSimple(content: React.ReactNode) {
return (
<IntlProvider
locale={intlLanguage}
messages={messages}
formats={intlFormats}
>
<MainContainer>
<ErrorMessage
message={
<FormattedMessage
id="errors.loading_type"
values={{ type: "configuration" }}
/>
}
error={config.error.message}
/>
</MainContainer>
<MainContainer>{content}</MainContainer>
</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 (
<ErrorBoundary>
<IntlProvider
Expand All @@ -332,10 +353,7 @@ export const App: React.FC = () => {
<ToastProvider>
<PluginsLoader>
<AppContainer>
<ConfigurationProvider
configuration={config.data?.configuration}
loading={config.loading}
>
<ConfigurationProvider configuration={config.data!.configuration}>
{maybeRenderReleaseNotes()}
<ConnectionMonitor />
<Suspense fallback={<LoadingIndicator />}>
Expand Down
4 changes: 2 additions & 2 deletions ui/v2.5/src/components/Dialogs/GenerateDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Icon } from "src/components/Shared/Icon";
import { useToast } from "src/hooks/Toast";
import * as GQL from "src/core/generated-graphql";
import { FormattedMessage, useIntl } from "react-intl";
import { ConfigurationContext } from "src/hooks/Config";
import { useConfigurationContext } from "src/hooks/Config";
import { Manual } from "../Help/Manual";
import { withoutTypename } from "src/utils/data";
import { GenerateOptions } from "../Settings/Tasks/GenerateOptions";
Expand All @@ -25,7 +25,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
onClose,
type,
}) => {
const { configuration } = React.useContext(ConfigurationContext);
const { configuration } = useConfigurationContext();

function getDefaultOptions(): GQL.GenerateMetadataInput {
return {
Expand Down
8 changes: 4 additions & 4 deletions ui/v2.5/src/components/FrontPage/Control.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useContext, useMemo } from "react";
import React, { useMemo } from "react";
import { useIntl } from "react-intl";
import { FrontPageContent, ICustomFilter } from "src/core/config";
import * as GQL from "src/core/generated-graphql";
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 { GalleryRecommendationRow } from "../Galleries/GalleryRecommendationRow";
import { ImageRecommendationRow } from "../Images/ImageRecommendationRow";
Expand Down Expand Up @@ -105,7 +105,7 @@ interface ISavedFilterResults {
const SavedFilterResults: React.FC<ISavedFilterResults> = ({
savedFilterID,
}) => {
const { configuration: config } = useContext(ConfigurationContext);
const { configuration: config } = useConfigurationContext();
const { loading, data } = useFindSavedFilter(savedFilterID.toString());

const filter = useMemo(() => {
Expand Down Expand Up @@ -136,7 +136,7 @@ interface ICustomFilterProps {
const CustomFilterResults: React.FC<ICustomFilterProps> = ({
customFilter,
}) => {
const { configuration: config } = useContext(ConfigurationContext);
const { configuration: config } = useConfigurationContext();
const intl = useIntl();

const filter = useMemo(() => {
Expand Down
6 changes: 3 additions & 3 deletions ui/v2.5/src/components/FrontPage/FrontPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Button } from "react-bootstrap";
import { FrontPageConfig } from "./FrontPageConfig";
import { useToast } from "src/hooks/Toast";
import { Control } from "./Control";
import { ConfigurationContext } from "src/hooks/Config";
import { useConfigurationContext } from "src/hooks/Config";
import {
FrontPageContent,
generateDefaultFrontPageContent,
Expand All @@ -24,7 +24,7 @@ const FrontPage: React.FC = PatchComponent("FrontPage", () => {

const [saveUI] = useConfigureUI();

const { configuration, loading } = React.useContext(ConfigurationContext);
const { configuration } = useConfigurationContext();

useScrollToTopOnMount();

Expand All @@ -51,7 +51,7 @@ const FrontPage: React.FC = PatchComponent("FrontPage", () => {
setSaving(false);
}

if (loading || saving) {
if (saving) {
return <LoadingIndicator />;
}

Expand Down
8 changes: 4 additions & 4 deletions ui/v2.5/src/components/FrontPage/FrontPageConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useFindSavedFilters } from "src/core/StashService";
import { LoadingIndicator } from "../Shared/LoadingIndicator";
import { Button, Form, Modal } from "react-bootstrap";
import * as GQL from "src/core/generated-graphql";
import { ConfigurationContext } from "src/hooks/Config";
import { useConfigurationContext } from "src/hooks/Config";
import {
ISavedFilterRow,
ICustomFilter,
Expand Down Expand Up @@ -277,11 +277,11 @@ interface IFrontPageConfigProps {
export const FrontPageConfig: React.FC<IFrontPageConfigProps> = ({
onClose,
}) => {
const { configuration, loading } = React.useContext(ConfigurationContext);
const { configuration } = useConfigurationContext();

const ui = configuration?.ui;

const { data: allFilters, loading: loading2 } = useFindSavedFilters();
const { data: allFilters, loading } = useFindSavedFilters();

const [isAdd, setIsAdd] = useState(false);
const [currentContent, setCurrentContent] = useState<FrontPageContent[]>([]);
Expand Down Expand Up @@ -338,7 +338,7 @@ export const FrontPageConfig: React.FC<IFrontPageConfigProps> = ({
setDragIndex(undefined);
}

if (loading || loading2) {
if (loading) {
return <LoadingIndicator />;
}

Expand Down
Loading