mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Scene queuing (#1214)
* Add missing localisation strings * Ignore container error in scene streams * Implement missing FindScenes by ID
This commit is contained in:
@@ -12,6 +12,8 @@ import {
|
||||
useSceneStreams,
|
||||
useSceneGenerateScreenshot,
|
||||
useSceneUpdate,
|
||||
queryFindScenes,
|
||||
queryFindScenesByID,
|
||||
} from "src/core/StashService";
|
||||
import { GalleryViewer } from "src/components/Galleries/GalleryViewer";
|
||||
import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared";
|
||||
@@ -19,6 +21,10 @@ import { useToast } from "src/hooks";
|
||||
import { ScenePlayer } from "src/components/ScenePlayer";
|
||||
import { TextUtils, JWUtils } from "src/utils";
|
||||
import Mousetrap from "mousetrap";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
import { FilterMode } from "src/models/list-filter/types";
|
||||
import { SceneQueue } from "src/models/sceneQueue";
|
||||
import { QueueViewer } from "./QueueViewer";
|
||||
import { SceneMarkersPanel } from "./SceneMarkersPanel";
|
||||
import { SceneFileInfoPanel } from "./SceneFileInfoPanel";
|
||||
import { SceneEditPanel } from "./SceneEditPanel";
|
||||
@@ -65,8 +71,59 @@ export const Scene: React.FC = () => {
|
||||
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
|
||||
const [isGenerateDialogOpen, setIsGenerateDialogOpen] = useState(false);
|
||||
|
||||
const [sceneQueue, setSceneQueue] = useState<SceneQueue>(new SceneQueue());
|
||||
const [queueScenes, setQueueScenes] = useState<GQL.SlimSceneDataFragment[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const [queueTotal, setQueueTotal] = useState(0);
|
||||
const [queueStart, setQueueStart] = useState(1);
|
||||
|
||||
const [rerenderPlayer, setRerenderPlayer] = useState(false);
|
||||
|
||||
const queryParams = queryString.parse(location.search);
|
||||
const autoplay = queryParams?.autoplay === "true";
|
||||
const currentQueueIndex = queueScenes.findIndex((s) => s.id === id);
|
||||
|
||||
async function getQueueFilterScenes(filter: ListFilterModel) {
|
||||
const query = await queryFindScenes(filter);
|
||||
const { scenes, count } = query.data.findScenes;
|
||||
setQueueScenes(scenes);
|
||||
setQueueTotal(count);
|
||||
setQueueStart((filter.currentPage - 1) * filter.itemsPerPage + 1);
|
||||
}
|
||||
|
||||
async function getQueueScenes(sceneIDs: number[]) {
|
||||
const query = await queryFindScenesByID(sceneIDs);
|
||||
const { scenes, count } = query.data.findScenes;
|
||||
setQueueScenes(scenes);
|
||||
setQueueTotal(count);
|
||||
setQueueStart(1);
|
||||
}
|
||||
|
||||
// HACK - jwplayer doesn't handle re-rendering when scene changes, so force
|
||||
// a rerender by not drawing it
|
||||
useEffect(() => {
|
||||
if (rerenderPlayer) {
|
||||
setRerenderPlayer(false);
|
||||
}
|
||||
}, [rerenderPlayer]);
|
||||
|
||||
useEffect(() => {
|
||||
setRerenderPlayer(true);
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
setSceneQueue(SceneQueue.fromQueryParameters(location.search));
|
||||
}, [location.search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (sceneQueue.query) {
|
||||
getQueueFilterScenes(sceneQueue.query);
|
||||
} else if (sceneQueue.sceneIDs) {
|
||||
getQueueScenes(sceneQueue.sceneIDs);
|
||||
}
|
||||
}, [sceneQueue]);
|
||||
|
||||
function getInitialTimestamp() {
|
||||
const params = queryString.parse(location.search);
|
||||
@@ -158,6 +215,99 @@ export const Scene: React.FC = () => {
|
||||
Toast.success({ content: "Generating screenshot" });
|
||||
}
|
||||
|
||||
async function onQueueLessScenes() {
|
||||
if (!sceneQueue.query || queueStart <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterCopy = Object.assign(
|
||||
new ListFilterModel(FilterMode.Scenes),
|
||||
sceneQueue.query
|
||||
);
|
||||
const newStart = queueStart - filterCopy.itemsPerPage;
|
||||
filterCopy.currentPage = Math.ceil(newStart / filterCopy.itemsPerPage);
|
||||
const query = await queryFindScenes(filterCopy);
|
||||
const { scenes } = query.data.findScenes;
|
||||
|
||||
// prepend scenes to scene list
|
||||
const newScenes = scenes.concat(queueScenes);
|
||||
setQueueScenes(newScenes);
|
||||
setQueueStart(newStart);
|
||||
}
|
||||
|
||||
function queueHasMoreScenes() {
|
||||
return queueStart + queueScenes.length - 1 < queueTotal;
|
||||
}
|
||||
|
||||
async function onQueueMoreScenes() {
|
||||
if (!sceneQueue.query || !queueHasMoreScenes()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterCopy = Object.assign(
|
||||
new ListFilterModel(FilterMode.Scenes),
|
||||
sceneQueue.query
|
||||
);
|
||||
const newStart = queueStart + queueScenes.length;
|
||||
filterCopy.currentPage = Math.ceil(newStart / filterCopy.itemsPerPage);
|
||||
const query = await queryFindScenes(filterCopy);
|
||||
const { scenes } = query.data.findScenes;
|
||||
|
||||
// append scenes to scene list
|
||||
const newScenes = scenes.concat(queueScenes);
|
||||
setQueueScenes(newScenes);
|
||||
// don't change queue start
|
||||
}
|
||||
|
||||
function playScene(sceneID: string, page?: number) {
|
||||
sceneQueue.playScene(history, sceneID, {
|
||||
newPage: page,
|
||||
autoPlay: true,
|
||||
});
|
||||
}
|
||||
|
||||
function onQueueNext() {
|
||||
if (currentQueueIndex >= 0 && currentQueueIndex < queueScenes.length - 1) {
|
||||
playScene(queueScenes[currentQueueIndex + 1].id);
|
||||
}
|
||||
}
|
||||
|
||||
function onQueuePrevious() {
|
||||
if (currentQueueIndex > 0) {
|
||||
playScene(queueScenes[currentQueueIndex - 1].id);
|
||||
}
|
||||
}
|
||||
|
||||
async function onQueueRandom() {
|
||||
if (sceneQueue.query) {
|
||||
const { query } = sceneQueue;
|
||||
const pages = Math.ceil(queueTotal / query.itemsPerPage);
|
||||
const page = Math.floor(Math.random() * pages) + 1;
|
||||
const index = Math.floor(Math.random() * query.itemsPerPage);
|
||||
const filterCopy = Object.assign(
|
||||
new ListFilterModel(FilterMode.Scenes),
|
||||
sceneQueue.query
|
||||
);
|
||||
filterCopy.currentPage = page;
|
||||
const queryResults = await queryFindScenes(filterCopy);
|
||||
if (queryResults.data.findScenes.scenes.length > index) {
|
||||
const { id: sceneID } = queryResults!.data!.findScenes!.scenes[index];
|
||||
// navigate to the image player page
|
||||
playScene(sceneID, page);
|
||||
}
|
||||
} else {
|
||||
const index = Math.floor(Math.random() * queueTotal);
|
||||
playScene(queueScenes[index].id);
|
||||
}
|
||||
}
|
||||
|
||||
function onComplete() {
|
||||
// load the next scene if we're autoplaying
|
||||
if (autoplay) {
|
||||
onQueueNext();
|
||||
}
|
||||
}
|
||||
|
||||
function onDeleteDialogClosed(deleted: boolean) {
|
||||
setIsDeleteAlertOpen(false);
|
||||
if (deleted) {
|
||||
@@ -255,6 +405,13 @@ export const Scene: React.FC = () => {
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="scene-details-panel">Details</Nav.Link>
|
||||
</Nav.Item>
|
||||
{(queueScenes ?? []).length > 0 ? (
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="scene-queue-panel">Queue</Nav.Link>
|
||||
</Nav.Item>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="scene-markers-panel">Markers</Nav.Link>
|
||||
</Nav.Item>
|
||||
@@ -310,6 +467,20 @@ export const Scene: React.FC = () => {
|
||||
<Tab.Pane eventKey="scene-details-panel">
|
||||
<SceneDetailPanel scene={scene} />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="scene-queue-panel">
|
||||
<QueueViewer
|
||||
scenes={queueScenes}
|
||||
currentID={scene.id}
|
||||
onSceneClicked={(sceneID) => playScene(sceneID)}
|
||||
onNext={onQueueNext}
|
||||
onPrevious={onQueuePrevious}
|
||||
onRandom={onQueueRandom}
|
||||
start={queueStart}
|
||||
hasMoreScenes={queueHasMoreScenes()}
|
||||
onLessScenes={() => onQueueLessScenes()}
|
||||
onMoreScenes={() => onQueueMoreScenes()}
|
||||
/>
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="scene-markers-panel">
|
||||
<SceneMarkersPanel
|
||||
scene={scene}
|
||||
@@ -414,13 +585,16 @@ export const Scene: React.FC = () => {
|
||||
</Button>
|
||||
</div>
|
||||
<div className={`scene-player-container ${collapsed ? "expanded" : ""}`}>
|
||||
<ScenePlayer
|
||||
className="w-100 m-sm-auto no-gutter"
|
||||
scene={scene}
|
||||
timestamp={timestamp}
|
||||
autoplay={autoplay}
|
||||
sceneStreams={sceneStreams?.sceneStreams ?? []}
|
||||
/>
|
||||
{!rerenderPlayer ? (
|
||||
<ScenePlayer
|
||||
className="w-100 m-sm-auto no-gutter"
|
||||
scene={scene}
|
||||
timestamp={timestamp}
|
||||
autoplay={autoplay}
|
||||
sceneStreams={sceneStreams?.sceneStreams ?? []}
|
||||
onComplete={onComplete}
|
||||
/>
|
||||
) : undefined}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user