Scene queuing (#1214)

* Add missing localisation strings
* Ignore container error in scene streams
* Implement missing FindScenes by ID
This commit is contained in:
WithoutPants
2021-03-31 14:36:11 +11:00
committed by GitHub
parent 496900df42
commit d5e9030768
17 changed files with 677 additions and 75 deletions

View File

@@ -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>
);