mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Add display autoplay and playlist continue options (#1921)
* Add display autoplay and playlist continue options * Include continue parameter in scene links
This commit is contained in:
parent
25274e2596
commit
3aa5f657bc
18 changed files with 202 additions and 35 deletions
|
|
@ -54,6 +54,8 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
|
|||
maximumLoopDuration
|
||||
noBrowser
|
||||
autostartVideo
|
||||
autostartVideoOnPlaySelected
|
||||
continuePlaylistDefault
|
||||
showStudioAsText
|
||||
css
|
||||
cssEnabled
|
||||
|
|
|
|||
|
|
@ -197,27 +197,40 @@ input ConfigDisableDropdownCreateInput {
|
|||
input ConfigInterfaceInput {
|
||||
"""Ordered list of items that should be shown in the menu"""
|
||||
menuItems: [String!]
|
||||
|
||||
"""Enable sound on mouseover previews"""
|
||||
soundOnPreview: Boolean
|
||||
|
||||
"""Show title and tags in wall view"""
|
||||
wallShowTitle: Boolean
|
||||
"""Wall playback type"""
|
||||
wallPlayback: String
|
||||
|
||||
"""Maximum duration (in seconds) in which a scene video will loop in the scene player"""
|
||||
maximumLoopDuration: Int
|
||||
"""If true, video will autostart on load in the scene player"""
|
||||
autostartVideo: Boolean
|
||||
"""If true, video will autostart when loading from play random or play selected"""
|
||||
autostartVideoOnPlaySelected: Boolean
|
||||
"""If true, next scene in playlist will be played at video end by default"""
|
||||
continuePlaylistDefault: Boolean
|
||||
|
||||
"""If true, studio overlays will be shown as text instead of logo images"""
|
||||
showStudioAsText: Boolean
|
||||
|
||||
"""Custom CSS"""
|
||||
css: String
|
||||
cssEnabled: Boolean
|
||||
|
||||
"""Interface language"""
|
||||
language: String
|
||||
|
||||
"""Slideshow Delay"""
|
||||
slideshowDelay: Int
|
||||
|
||||
"""Set to true to disable creating new objects via the dropdown menus"""
|
||||
disableDropdownCreate: ConfigDisableDropdownCreateInput
|
||||
|
||||
"""Handy Connection Key"""
|
||||
handyKey: String
|
||||
"""Funscript Time Offset"""
|
||||
|
|
@ -235,29 +248,42 @@ type ConfigDisableDropdownCreate {
|
|||
type ConfigInterfaceResult {
|
||||
"""Ordered list of items that should be shown in the menu"""
|
||||
menuItems: [String!]
|
||||
|
||||
"""Enable sound on mouseover previews"""
|
||||
soundOnPreview: Boolean
|
||||
|
||||
"""Show title and tags in wall view"""
|
||||
wallShowTitle: Boolean
|
||||
"""Wall playback type"""
|
||||
wallPlayback: String
|
||||
|
||||
"""Maximum duration (in seconds) in which a scene video will loop in the scene player"""
|
||||
maximumLoopDuration: Int
|
||||
""""True if we should not auto-open a browser window on startup"""
|
||||
noBrowser: Boolean
|
||||
"""If true, video will autostart on load in the scene player"""
|
||||
autostartVideo: Boolean
|
||||
"""If true, video will autostart when loading from play random or play selected"""
|
||||
autostartVideoOnPlaySelected: Boolean
|
||||
"""If true, next scene in playlist will be played at video end by default"""
|
||||
continuePlaylistDefault: Boolean
|
||||
|
||||
"""If true, studio overlays will be shown as text instead of logo images"""
|
||||
showStudioAsText: Boolean
|
||||
|
||||
"""Custom CSS"""
|
||||
css: String
|
||||
cssEnabled: Boolean
|
||||
|
||||
"""Interface language"""
|
||||
language: String
|
||||
|
||||
"""Slideshow Delay"""
|
||||
slideshowDelay: Int
|
||||
|
||||
"""Fields are true if creating via dropdown menus are disabled"""
|
||||
disabledDropdownCreate: ConfigDisableDropdownCreate!
|
||||
|
||||
"""Handy Connection Key"""
|
||||
handyKey: String
|
||||
"""Funscript Time Offset"""
|
||||
|
|
|
|||
|
|
@ -251,6 +251,8 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
|
|||
|
||||
setBool(config.AutostartVideo, input.AutostartVideo)
|
||||
setBool(config.ShowStudioAsText, input.ShowStudioAsText)
|
||||
setBool(config.AutostartVideoOnPlaySelected, input.AutostartVideoOnPlaySelected)
|
||||
setBool(config.ContinuePlaylistDefault, input.ContinuePlaylistDefault)
|
||||
|
||||
if input.Language != nil {
|
||||
c.Set(config.Language, *input.Language)
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
|
|||
noBrowser := config.GetNoBrowserFlag()
|
||||
maximumLoopDuration := config.GetMaximumLoopDuration()
|
||||
autostartVideo := config.GetAutostartVideo()
|
||||
autostartVideoOnPlaySelected := config.GetAutostartVideoOnPlaySelected()
|
||||
continuePlaylistDefault := config.GetContinuePlaylistDefault()
|
||||
showStudioAsText := config.GetShowStudioAsText()
|
||||
css := config.GetCSS()
|
||||
cssEnabled := config.GetCSSEnabled()
|
||||
|
|
@ -120,21 +122,23 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
|
|||
scriptOffset := config.GetFunscriptOffset()
|
||||
|
||||
return &models.ConfigInterfaceResult{
|
||||
MenuItems: menuItems,
|
||||
SoundOnPreview: &soundOnPreview,
|
||||
WallShowTitle: &wallShowTitle,
|
||||
WallPlayback: &wallPlayback,
|
||||
MaximumLoopDuration: &maximumLoopDuration,
|
||||
NoBrowser: &noBrowser,
|
||||
AutostartVideo: &autostartVideo,
|
||||
ShowStudioAsText: &showStudioAsText,
|
||||
CSS: &css,
|
||||
CSSEnabled: &cssEnabled,
|
||||
Language: &language,
|
||||
SlideshowDelay: &slideshowDelay,
|
||||
DisabledDropdownCreate: config.GetDisableDropdownCreate(),
|
||||
HandyKey: &handyKey,
|
||||
FunscriptOffset: &scriptOffset,
|
||||
MenuItems: menuItems,
|
||||
SoundOnPreview: &soundOnPreview,
|
||||
WallShowTitle: &wallShowTitle,
|
||||
WallPlayback: &wallPlayback,
|
||||
MaximumLoopDuration: &maximumLoopDuration,
|
||||
NoBrowser: &noBrowser,
|
||||
AutostartVideo: &autostartVideo,
|
||||
ShowStudioAsText: &showStudioAsText,
|
||||
AutostartVideoOnPlaySelected: &autostartVideoOnPlaySelected,
|
||||
ContinuePlaylistDefault: &continuePlaylistDefault,
|
||||
CSS: &css,
|
||||
CSSEnabled: &cssEnabled,
|
||||
Language: &language,
|
||||
SlideshowDelay: &slideshowDelay,
|
||||
DisabledDropdownCreate: config.GetDisableDropdownCreate(),
|
||||
HandyKey: &handyKey,
|
||||
FunscriptOffset: &scriptOffset,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -130,6 +130,8 @@ const WallShowTitle = "wall_show_title"
|
|||
const CustomPerformerImageLocation = "custom_performer_image_location"
|
||||
const MaximumLoopDuration = "maximum_loop_duration"
|
||||
const AutostartVideo = "autostart_video"
|
||||
const AutostartVideoOnPlaySelected = "autostart_video_on_play_selected"
|
||||
const ContinuePlaylistDefault = "continue_playlist_default"
|
||||
const ShowStudioAsText = "show_studio_as_text"
|
||||
const CSSEnabled = "cssEnabled"
|
||||
const WallPlayback = "wall_playback"
|
||||
|
|
@ -808,6 +810,20 @@ func (i *Instance) GetAutostartVideo() bool {
|
|||
return viper.GetBool(AutostartVideo)
|
||||
}
|
||||
|
||||
func (i *Instance) GetAutostartVideoOnPlaySelected() bool {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(AutostartVideoOnPlaySelected, true)
|
||||
return viper.GetBool(AutostartVideoOnPlaySelected)
|
||||
}
|
||||
|
||||
func (i *Instance) GetContinuePlaylistDefault() bool {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(ContinuePlaylistDefault, false)
|
||||
return viper.GetBool(ContinuePlaylistDefault)
|
||||
}
|
||||
|
||||
func (i *Instance) GetShowStudioAsText() bool {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ func TestConcurrentConfigAccess(t *testing.T) {
|
|||
i.Set(MaxUploadSize, i.GetMaxUploadSize())
|
||||
i.Set(FunscriptOffset, i.GetFunscriptOffset())
|
||||
i.Set(DefaultIdentifySettings, i.GetDefaultIdentifySettings())
|
||||
i.Set(AutostartVideoOnPlaySelected, i.GetAutostartVideoOnPlaySelected())
|
||||
i.Set(ContinuePlaylistDefault, i.GetContinuePlaylistDefault())
|
||||
}
|
||||
wg.Done()
|
||||
}(k)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
### ✨ New Features
|
||||
* Add options to auto-start videos when playing from selection and continue to scene playlists. ([#1921](https://github.com/stashapp/stash/pull/1921))
|
||||
* Support is (not) null for multi-relational filter criteria. ([#1785](https://github.com/stashapp/stash/pull/1785))
|
||||
* Optionally open browser on startup (enabled by default for new systems). ([#1832](https://github.com/stashapp/stash/pull/1832))
|
||||
* Support setting defaults for Delete File and Delete Generated Files in the Interface Settings. ([#1852](https://github.com/stashapp/stash/pull/1852))
|
||||
|
|
|
|||
|
|
@ -88,13 +88,8 @@ export const GalleryList: React.FC<IGalleryList> = ({
|
|||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindGalleries(filterCopy);
|
||||
if (
|
||||
singleResult &&
|
||||
singleResult.data &&
|
||||
singleResult.data.findGalleries &&
|
||||
singleResult.data.findGalleries.galleries.length === 1
|
||||
) {
|
||||
const { id } = singleResult!.data!.findGalleries!.galleries[0];
|
||||
if (singleResult.data.findGalleries.galleries.length === 1) {
|
||||
const { id } = singleResult.data.findGalleries.galleries[0];
|
||||
// navigate to the image player page
|
||||
history.push(`/galleries/${id}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -305,8 +305,13 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||
}
|
||||
}
|
||||
|
||||
const cont = configuration?.interface.continuePlaylistDefault ?? false;
|
||||
|
||||
const sceneLink = props.queue
|
||||
? props.queue.makeLink(props.scene.id, { sceneIndex: props.index })
|
||||
? props.queue.makeLink(props.scene.id, {
|
||||
sceneIndex: props.index,
|
||||
continue: cont,
|
||||
})
|
||||
: `/scenes/${props.scene.id}`;
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -3,14 +3,17 @@ import { Link } from "react-router-dom";
|
|||
import cx from "classnames";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { TextUtils } from "src/utils";
|
||||
import { Button, Spinner } from "react-bootstrap";
|
||||
import { Button, Form, Spinner } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
export interface IPlaylistViewer {
|
||||
scenes?: GQL.SlimSceneDataFragment[];
|
||||
currentID?: string;
|
||||
start?: number;
|
||||
continue?: boolean;
|
||||
hasMoreScenes: boolean;
|
||||
setContinue: (v: boolean) => void;
|
||||
onSceneClicked: (id: string) => void;
|
||||
onNext: () => void;
|
||||
onPrevious: () => void;
|
||||
|
|
@ -23,7 +26,9 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
|
|||
scenes,
|
||||
currentID,
|
||||
start,
|
||||
continue: continuePlaylist = false,
|
||||
hasMoreScenes,
|
||||
setContinue,
|
||||
onNext,
|
||||
onPrevious,
|
||||
onRandom,
|
||||
|
|
@ -31,6 +36,7 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
|
|||
onMoreScenes,
|
||||
onLessScenes,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [lessLoading, setLessLoading] = useState(false);
|
||||
const [moreLoading, setMoreLoading] = useState(false);
|
||||
|
||||
|
|
@ -91,6 +97,17 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
|
|||
return (
|
||||
<div id="queue-viewer">
|
||||
<div className="queue-controls">
|
||||
<div>
|
||||
<Form.Check
|
||||
checked={continuePlaylist}
|
||||
label={intl.formatMessage({
|
||||
id: "continue",
|
||||
})}
|
||||
onChange={() => {
|
||||
setContinue(!continuePlaylist);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{(currentIndex ?? 0) > 0 ? (
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Tab, Nav, Dropdown, Button, ButtonGroup } from "react-bootstrap";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useParams, useLocation, useHistory, Link } from "react-router-dom";
|
||||
import { Helmet } from "react-helmet";
|
||||
|
|
@ -79,10 +79,13 @@ const ScenePage: React.FC<IProps> = ({ scene, refetch }) => {
|
|||
|
||||
const [queueTotal, setQueueTotal] = useState(0);
|
||||
const [queueStart, setQueueStart] = useState(1);
|
||||
const [continuePlaylist, setContinuePlaylist] = useState(false);
|
||||
|
||||
const [rerenderPlayer, setRerenderPlayer] = useState(false);
|
||||
|
||||
const queryParams = queryString.parse(location.search);
|
||||
const queryParams = useMemo(() => queryString.parse(location.search), [
|
||||
location.search,
|
||||
]);
|
||||
const autoplay = queryParams?.autoplay === "true";
|
||||
const currentQueueIndex = queueScenes.findIndex((s) => s.id === scene.id);
|
||||
|
||||
|
|
@ -102,6 +105,10 @@ const ScenePage: React.FC<IProps> = ({ scene, refetch }) => {
|
|||
setQueueStart(1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setContinuePlaylist(queryParams?.continue === "true");
|
||||
}, [queryParams]);
|
||||
|
||||
// HACK - jwplayer doesn't handle re-rendering when scene changes, so force
|
||||
// a rerender by not drawing it
|
||||
useEffect(() => {
|
||||
|
|
@ -262,6 +269,7 @@ const ScenePage: React.FC<IProps> = ({ scene, refetch }) => {
|
|||
sceneQueue.playScene(history, sceneID, {
|
||||
newPage: page,
|
||||
autoPlay: true,
|
||||
continue: continuePlaylist,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -301,7 +309,7 @@ const ScenePage: React.FC<IProps> = ({ scene, refetch }) => {
|
|||
|
||||
function onComplete() {
|
||||
// load the next scene if we're autoplaying
|
||||
if (autoplay) {
|
||||
if (continuePlaylist) {
|
||||
onQueueNext();
|
||||
}
|
||||
}
|
||||
|
|
@ -485,6 +493,8 @@ const ScenePage: React.FC<IProps> = ({ scene, refetch }) => {
|
|||
<QueueViewer
|
||||
scenes={queueScenes}
|
||||
currentID={scene.id}
|
||||
continue={continuePlaylist}
|
||||
setContinue={(v) => setContinuePlaylist(v)}
|
||||
onSceneClicked={(sceneID) => playScene(sceneID)}
|
||||
onNext={onQueueNext}
|
||||
onPrevious={onQueuePrevious}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { ExportDialog } from "../Shared/ExportDialog";
|
|||
import { SceneCardsGrid } from "./SceneCardsGrid";
|
||||
import { TaggerContext } from "../Tagger/context";
|
||||
import { IdentifyDialog } from "../Dialogs/IdentifyDialog/IdentifyDialog";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
|
||||
interface ISceneList {
|
||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||
|
|
@ -38,6 +39,7 @@ export const SceneList: React.FC<ISceneList> = ({
|
|||
}) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const config = React.useContext(ConfigurationContext);
|
||||
const [isGenerateDialogOpen, setIsGenerateDialogOpen] = useState(false);
|
||||
const [isIdentifyDialogOpen, setIsIdentifyDialogOpen] = useState(false);
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
|
|
@ -114,7 +116,11 @@ export const SceneList: React.FC<ISceneList> = ({
|
|||
// populate queue and go to first scene
|
||||
const sceneIDs = Array.from(selectedIds.values());
|
||||
const queue = SceneQueue.fromSceneIDList(sceneIDs);
|
||||
queue.playScene(history, sceneIDs[0], { autoPlay: true });
|
||||
const autoPlay =
|
||||
config.configuration?.interface.autostartVideoOnPlaySelected ?? false;
|
||||
const cont =
|
||||
config.configuration?.interface.continuePlaylistDefault ?? false;
|
||||
queue.playScene(history, sceneIDs[0], { autoPlay, continue: cont });
|
||||
}
|
||||
|
||||
async function playRandom(
|
||||
|
|
@ -127,7 +133,10 @@ export const SceneList: React.FC<ISceneList> = ({
|
|||
|
||||
const pages = Math.ceil(count / filter.itemsPerPage);
|
||||
const page = Math.floor(Math.random() * pages) + 1;
|
||||
const index = Math.floor(Math.random() * filter.itemsPerPage);
|
||||
|
||||
const indexMax =
|
||||
filter.itemsPerPage < count ? filter.itemsPerPage : count;
|
||||
const index = Math.floor(Math.random() * indexMax);
|
||||
const filterCopy = _.cloneDeep(filter);
|
||||
filterCopy.currentPage = page;
|
||||
filterCopy.sortBy = "random";
|
||||
|
|
@ -136,7 +145,15 @@ export const SceneList: React.FC<ISceneList> = ({
|
|||
const { id } = queryResults!.data!.findScenes!.scenes[index];
|
||||
// navigate to the image player page
|
||||
const queue = SceneQueue.fromListFilterModel(filterCopy);
|
||||
queue.playScene(history, id, { sceneIndex: index, autoPlay: true });
|
||||
const autoPlay =
|
||||
config.configuration?.interface.autostartVideoOnPlaySelected ?? false;
|
||||
const cont =
|
||||
config.configuration?.interface.continuePlaylistDefault ?? false;
|
||||
queue.playScene(history, id, {
|
||||
sceneIndex: index,
|
||||
autoPlay,
|
||||
continue: cont,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -543,9 +543,10 @@ input[type="range"].blue-slider {
|
|||
|
||||
#queue-viewer {
|
||||
.queue-controls {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
justify-content: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.thumbnail-container {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||
const [wallPlayback, setWallPlayback] = useState<string>("video");
|
||||
const [maximumLoopDuration, setMaximumLoopDuration] = useState<number>(0);
|
||||
const [autostartVideo, setAutostartVideo] = useState<boolean>(false);
|
||||
const [
|
||||
autostartVideoOnPlaySelected,
|
||||
setAutostartVideoOnPlaySelected,
|
||||
] = useState(true);
|
||||
const [continuePlaylistDefault, setContinuePlaylistDefault] = useState(false);
|
||||
const [slideshowDelay, setSlideshowDelay] = useState<number>(0);
|
||||
const [showStudioAsText, setShowStudioAsText] = useState<boolean>(false);
|
||||
const [css, setCSS] = useState<string>();
|
||||
|
|
@ -62,6 +67,8 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||
maximumLoopDuration,
|
||||
noBrowser,
|
||||
autostartVideo,
|
||||
autostartVideoOnPlaySelected,
|
||||
continuePlaylistDefault,
|
||||
showStudioAsText,
|
||||
css,
|
||||
cssEnabled,
|
||||
|
|
@ -84,6 +91,10 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||
setMaximumLoopDuration(iCfg.maximumLoopDuration ?? 0);
|
||||
setNoBrowserFlag(iCfg?.noBrowser ?? false);
|
||||
setAutostartVideo(iCfg.autostartVideo ?? false);
|
||||
setAutostartVideoOnPlaySelected(
|
||||
iCfg.autostartVideoOnPlaySelected ?? true
|
||||
);
|
||||
setContinuePlaylistDefault(iCfg.continuePlaylistDefault ?? false);
|
||||
setShowStudioAsText(iCfg.showStudioAsText ?? false);
|
||||
setCSS(iCfg.css ?? "");
|
||||
setCSSEnabled(iCfg.cssEnabled ?? false);
|
||||
|
|
@ -286,6 +297,43 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||
}}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group id="auto-start-video-on-play-selected">
|
||||
<Form.Check
|
||||
checked={autostartVideoOnPlaySelected}
|
||||
label={intl.formatMessage({
|
||||
id:
|
||||
"config.ui.scene_player.options.auto_start_video_on_play_selected.heading",
|
||||
})}
|
||||
onChange={() => {
|
||||
setAutostartVideoOnPlaySelected(!autostartVideoOnPlaySelected);
|
||||
}}
|
||||
/>
|
||||
<Form.Text className="text-muted">
|
||||
{intl.formatMessage({
|
||||
id:
|
||||
"config.ui.scene_player.options.auto_start_video_on_play_selected.description",
|
||||
})}
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group id="continue-playlist-default">
|
||||
<Form.Check
|
||||
checked={continuePlaylistDefault}
|
||||
label={intl.formatMessage({
|
||||
id:
|
||||
"config.ui.scene_player.options.continue_playlist_default.heading",
|
||||
})}
|
||||
onChange={() => {
|
||||
setContinuePlaylistDefault(!continuePlaylistDefault);
|
||||
}}
|
||||
/>
|
||||
<Form.Text className="text-muted">
|
||||
{intl.formatMessage({
|
||||
id:
|
||||
"config.ui.scene_player.options.continue_playlist_default.description",
|
||||
})}
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group id="max-loop-duration">
|
||||
<h6>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import Config from "./Config";
|
|||
import { TaggerScene } from "./TaggerScene";
|
||||
import { SceneTaggerModals } from "./sceneTaggerModals";
|
||||
import { SceneSearchResults } from "./StashSearchResult";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
|
||||
interface ITaggerProps {
|
||||
scenes: GQL.SlimSceneDataFragment[];
|
||||
|
|
@ -33,14 +34,18 @@ export const Tagger: React.FC<ITaggerProps> = ({ scenes, queue }) => {
|
|||
submitFingerprints,
|
||||
pendingFingerprints,
|
||||
} = useContext(TaggerStateContext);
|
||||
const { configuration } = React.useContext(ConfigurationContext);
|
||||
|
||||
const [showConfig, setShowConfig] = useState(false);
|
||||
const [hideUnmatched, setHideUnmatched] = useState(false);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const cont = configuration?.interface.continuePlaylistDefault ?? false;
|
||||
|
||||
function generateSceneLink(scene: GQL.SlimSceneDataFragment, index: number) {
|
||||
return queue
|
||||
? queue.makeLink(scene.id, { sceneIndex: index })
|
||||
? queue.makeLink(scene.id, { sceneIndex: index, continue: cont })
|
||||
: `/scenes/${scene.id}`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -161,11 +161,16 @@ export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
|||
}
|
||||
};
|
||||
|
||||
const cont = config?.interface.continuePlaylistDefault ?? false;
|
||||
|
||||
let linkSrc: string = "#";
|
||||
if (!props.clickHandler) {
|
||||
if (props.scene) {
|
||||
linkSrc = props.sceneQueue
|
||||
? props.sceneQueue.makeLink(props.scene.id, { sceneIndex: props.index })
|
||||
? props.sceneQueue.makeLink(props.scene.id, {
|
||||
sceneIndex: props.index,
|
||||
continue: cont,
|
||||
})
|
||||
: `/scenes/${props.scene.id}`;
|
||||
} else if (props.sceneMarker) {
|
||||
linkSrc = NavUtils.makeSceneMarkerUrl(props.sceneMarker);
|
||||
|
|
|
|||
|
|
@ -437,7 +437,15 @@
|
|||
"scene_player": {
|
||||
"heading": "Scene Player",
|
||||
"options": {
|
||||
"auto_start_video": "Auto-start video"
|
||||
"auto_start_video": "Auto-start video",
|
||||
"auto_start_video_on_play_selected": {
|
||||
"heading": "Auto-start video when playing selected",
|
||||
"description": "Auto-start scene videos when playing selected or random from Scenes page"
|
||||
},
|
||||
"continue_playlist_default": {
|
||||
"heading": "Continue playlist by default",
|
||||
"description": "Play next scene in queue when video finishes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scene_wall": {
|
||||
|
|
@ -455,6 +463,7 @@
|
|||
}
|
||||
},
|
||||
"configuration": "Configuration",
|
||||
"continue": "Continue",
|
||||
"countables": {
|
||||
"galleries": "{count, plural, one {Gallery} other {Galleries}}",
|
||||
"images": "{count, plural, one {Image} other {Images}}",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export interface IPlaySceneOptions {
|
|||
sceneIndex?: number;
|
||||
newPage?: number;
|
||||
autoPlay?: boolean;
|
||||
continue?: boolean;
|
||||
}
|
||||
|
||||
export class SceneQueue {
|
||||
|
|
@ -125,6 +126,7 @@ export class SceneQueue {
|
|||
options?.newPage
|
||||
);
|
||||
const autoplayParam = options?.autoPlay ? "&autoplay=true" : "";
|
||||
return `/scenes/${sceneID}?${paramStr}${autoplayParam}`;
|
||||
const continueParam = options?.continue ? "&continue=true" : "";
|
||||
return `/scenes/${sceneID}?${paramStr}${autoplayParam}${continueParam}`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue