mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Apply scene queuing for all scene listviews (#1332)
* Apply queue population for all scene list views * Add missing localisation strings
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
TruncatedText,
|
||||
} from "src/components/Shared";
|
||||
import { TextUtils } from "src/utils";
|
||||
import { SceneQueue } from "src/models/sceneQueue";
|
||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||
|
||||
interface IScenePreviewProps {
|
||||
@@ -65,12 +66,13 @@ export const ScenePreview: React.FC<IScenePreviewProps> = ({
|
||||
|
||||
interface ISceneCardProps {
|
||||
scene: GQL.SlimSceneDataFragment;
|
||||
index?: number;
|
||||
queue?: SceneQueue;
|
||||
compact?: boolean;
|
||||
selecting?: boolean;
|
||||
selected?: boolean | undefined;
|
||||
zoomIndex?: number;
|
||||
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
|
||||
onSceneClicked?: () => void;
|
||||
}
|
||||
|
||||
export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
@@ -300,9 +302,6 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
if (props.selecting && props.onSelectedChanged) {
|
||||
props.onSelectedChanged(!props.selected, shiftKey);
|
||||
event.preventDefault();
|
||||
} else if (props.onSceneClicked) {
|
||||
props.onSceneClicked();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +339,10 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
|
||||
let shiftKey = false;
|
||||
|
||||
const sceneLink = props.queue
|
||||
? props.queue.makeLink(props.scene.id, { sceneIndex: props.index })
|
||||
: `/scenes/${props.scene.id}`;
|
||||
|
||||
return (
|
||||
<Card className={`scene-card ${zoomIndex()}`}>
|
||||
<Form.Control
|
||||
@@ -356,7 +359,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
|
||||
<div className="video-section">
|
||||
<Link
|
||||
to={`/scenes/${props.scene.id}`}
|
||||
to={sceneLink}
|
||||
className="scene-card-link"
|
||||
onClick={handleSceneClick}
|
||||
onDragStart={handleDrag}
|
||||
|
||||
@@ -1,41 +1,37 @@
|
||||
import React from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { SceneQueue } from "src/models/sceneQueue";
|
||||
import { SceneCard } from "./SceneCard";
|
||||
|
||||
interface ISceneCardsGrid {
|
||||
scenes: GQL.SlimSceneDataFragment[];
|
||||
queue?: SceneQueue;
|
||||
selectedIds: Set<string>;
|
||||
zoomIndex: number;
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
||||
onSceneClick?: (id: string, index: number) => void;
|
||||
}
|
||||
|
||||
export const SceneCardsGrid: React.FC<ISceneCardsGrid> = ({
|
||||
scenes,
|
||||
queue,
|
||||
selectedIds,
|
||||
zoomIndex,
|
||||
onSelectChange,
|
||||
onSceneClick,
|
||||
}) => {
|
||||
function sceneClicked(sceneID: string, index: number) {
|
||||
if (onSceneClick) {
|
||||
onSceneClick(sceneID, index);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row justify-content-center">
|
||||
{scenes.map((scene, index) => (
|
||||
<SceneCard
|
||||
key={scene.id}
|
||||
scene={scene}
|
||||
queue={queue}
|
||||
index={index}
|
||||
zoomIndex={zoomIndex}
|
||||
selecting={selectedIds.size > 0}
|
||||
selected={selectedIds.has(scene.id)}
|
||||
onSelectedChanged={(selected: boolean, shiftKey: boolean) =>
|
||||
onSelectChange(scene.id, selected, shiftKey)
|
||||
}
|
||||
onSceneClicked={() => sceneClicked(scene.id, index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -123,8 +123,8 @@ export const SceneList: React.FC<ISceneList> = ({
|
||||
if (queryResults.data.findScenes.scenes.length > index) {
|
||||
const { id } = queryResults!.data!.findScenes!.scenes[index];
|
||||
// navigate to the image player page
|
||||
const queue = SceneQueue.fromListFilterModel(filterCopy, index);
|
||||
queue.playScene(history, id, { autoPlay: true });
|
||||
const queue = SceneQueue.fromListFilterModel(filterCopy);
|
||||
queue.playScene(history, id, { sceneIndex: index, autoPlay: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,15 +143,6 @@ export const SceneList: React.FC<ISceneList> = ({
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
async function sceneClicked(
|
||||
sceneId: string,
|
||||
sceneIndex: number,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
const queue = SceneQueue.fromListFilterModel(filter, sceneIndex);
|
||||
queue.playScene(history, sceneId);
|
||||
}
|
||||
|
||||
function maybeRenderSceneGenerateDialog(selectedIds: Set<string>) {
|
||||
if (isGenerateDialogOpen) {
|
||||
return (
|
||||
@@ -207,27 +198,34 @@ export const SceneList: React.FC<ISceneList> = ({
|
||||
if (!result.data || !result.data.findScenes) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queue = SceneQueue.fromListFilterModel(filter);
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<SceneCardsGrid
|
||||
scenes={result.data.findScenes.scenes}
|
||||
queue={queue}
|
||||
zoomIndex={zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={(id, selected, shiftKey) =>
|
||||
listData.onSelectChange(id, selected, shiftKey)
|
||||
}
|
||||
onSceneClick={(id, index) => sceneClicked(id, index, filter)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
return <SceneListTable scenes={result.data.findScenes.scenes} />;
|
||||
return (
|
||||
<SceneListTable scenes={result.data.findScenes.scenes} queue={queue} />
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return <WallPanel scenes={result.data.findScenes.scenes} />;
|
||||
return (
|
||||
<WallPanel scenes={result.data.findScenes.scenes} sceneQueue={queue} />
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Tagger) {
|
||||
return <Tagger scenes={result.data.findScenes.scenes} />;
|
||||
return <Tagger scenes={result.data.findScenes.scenes} queue={queue} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Icon, TruncatedText } from "src/components/Shared";
|
||||
|
||||
interface ISceneListTableProps {
|
||||
scenes: GQL.SlimSceneDataFragment[];
|
||||
queue?: SceneQueue;
|
||||
}
|
||||
|
||||
export const SceneListTable: React.FC<ISceneListTableProps> = (
|
||||
@@ -37,10 +38,15 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
|
||||
)
|
||||
);
|
||||
|
||||
const renderSceneRow = (scene: GQL.SlimSceneDataFragment) => (
|
||||
const renderSceneRow = (scene: GQL.SlimSceneDataFragment, index: number) => {
|
||||
const sceneLink = props.queue
|
||||
? props.queue.makeLink(scene.id, { sceneIndex: index })
|
||||
: `/scenes/${scene.id}`;
|
||||
|
||||
return (
|
||||
<tr key={scene.id}>
|
||||
<td>
|
||||
<Link to={`/scenes/${scene.id}`}>
|
||||
<Link to={sceneLink}>
|
||||
<img
|
||||
className="image-thumbnail"
|
||||
alt={scene.title ?? ""}
|
||||
@@ -49,7 +55,7 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
|
||||
</Link>
|
||||
</td>
|
||||
<td className="text-left">
|
||||
<Link to={`/scenes/${scene.id}`}>
|
||||
<Link to={sceneLink}>
|
||||
<h5>
|
||||
<TruncatedText
|
||||
text={scene.title ?? TextUtils.fileNameFromPath(scene.path)}
|
||||
@@ -83,6 +89,7 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="row table-list justify-content-center">
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "src/core/StashService";
|
||||
import { Manual } from "src/components/Help/Manual";
|
||||
|
||||
import { SceneQueue } from "src/models/sceneQueue";
|
||||
import StashSearchResult from "./StashSearchResult";
|
||||
import Config from "./Config";
|
||||
import {
|
||||
@@ -141,6 +142,7 @@ function prepareQueryString(
|
||||
|
||||
interface ITaggerListProps {
|
||||
scenes: GQL.SlimSceneDataFragment[];
|
||||
queue?: SceneQueue;
|
||||
selectedEndpoint: { endpoint: string; index: number };
|
||||
config: ITaggerConfig;
|
||||
queueFingerprintSubmission: (sceneId: string, endpoint: string) => void;
|
||||
@@ -149,6 +151,7 @@ interface ITaggerListProps {
|
||||
|
||||
const TaggerList: React.FC<ITaggerListProps> = ({
|
||||
scenes,
|
||||
queue,
|
||||
selectedEndpoint,
|
||||
config,
|
||||
queueFingerprintSubmission,
|
||||
@@ -304,8 +307,15 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
||||
setHideUnmatched(!hideUnmatched);
|
||||
};
|
||||
|
||||
function generateSceneLink(scene: GQL.SlimSceneDataFragment, index: number) {
|
||||
return queue
|
||||
? queue.makeLink(scene.id, { sceneIndex: index })
|
||||
: `/scenes/${scene.id}`;
|
||||
}
|
||||
|
||||
const renderScenes = () =>
|
||||
scenes.map((scene) => {
|
||||
scenes.map((scene, index) => {
|
||||
const sceneLink = generateSceneLink(scene, index);
|
||||
const { paths, file, ext } = parsePath(scene.path);
|
||||
const originalDir = scene.path.slice(
|
||||
0,
|
||||
@@ -376,7 +386,7 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
||||
<div className="d-flex flex-column text-right">
|
||||
<h5>Scene successfully tagged:</h5>
|
||||
<h6>
|
||||
<Link className="bold" to={`/scenes/${scene.id}`}>
|
||||
<Link className="bold" to={sceneLink}>
|
||||
{taggedScenes[scene.id].title}
|
||||
</Link>
|
||||
</h6>
|
||||
@@ -476,7 +486,7 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
||||
<div className="row">
|
||||
<div className="col col-lg-6 overflow-hidden align-items-center d-flex flex-column flex-sm-row">
|
||||
<div className="scene-card mr-3">
|
||||
<Link to={`/scenes/${scene.id}`}>
|
||||
<Link to={sceneLink}>
|
||||
<ScenePreview
|
||||
image={scene.paths.screenshot ?? undefined}
|
||||
video={scene.paths.preview ?? undefined}
|
||||
@@ -485,10 +495,7 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<Link
|
||||
to={`/scenes/${scene.id}`}
|
||||
className="scene-link overflow-hidden"
|
||||
>
|
||||
<Link to={sceneLink} className="scene-link overflow-hidden">
|
||||
<TruncatedText
|
||||
text={`${originalDir}\u200B${file}${ext}`}
|
||||
lineCount={2}
|
||||
@@ -548,9 +555,10 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
||||
|
||||
interface ITaggerProps {
|
||||
scenes: GQL.SlimSceneDataFragment[];
|
||||
queue?: SceneQueue;
|
||||
}
|
||||
|
||||
export const Tagger: React.FC<ITaggerProps> = ({ scenes }) => {
|
||||
export const Tagger: React.FC<ITaggerProps> = ({ scenes, queue }) => {
|
||||
const stashConfig = useConfiguration();
|
||||
const [{ data: config }, setConfig] = useLocalForage<ITaggerConfig>(
|
||||
LOCAL_FORAGE_KEY,
|
||||
@@ -620,6 +628,7 @@ export const Tagger: React.FC<ITaggerProps> = ({ scenes }) => {
|
||||
<Config config={config} setConfig={setConfig} show={showConfig} />
|
||||
<TaggerList
|
||||
scenes={scenes}
|
||||
queue={queue}
|
||||
config={config}
|
||||
selectedEndpoint={{
|
||||
endpoint: selectedEndpoint.endpoint,
|
||||
|
||||
@@ -4,9 +4,12 @@ import * as GQL from "src/core/generated-graphql";
|
||||
import { useConfiguration } from "src/core/StashService";
|
||||
import { TextUtils, NavUtils } from "src/utils";
|
||||
import cx from "classnames";
|
||||
import { SceneQueue } from "src/models/sceneQueue";
|
||||
|
||||
interface IWallItemProps {
|
||||
index?: number;
|
||||
scene?: GQL.SlimSceneDataFragment;
|
||||
sceneQueue?: SceneQueue;
|
||||
sceneMarker?: GQL.SceneMarkerDataFragment;
|
||||
image?: GQL.SlimImageDataFragment;
|
||||
clickHandler?: (
|
||||
@@ -161,7 +164,9 @@ export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
||||
let linkSrc: string = "#";
|
||||
if (!props.clickHandler) {
|
||||
if (props.scene) {
|
||||
linkSrc = `/scenes/${props.scene.id}`;
|
||||
linkSrc = props.sceneQueue
|
||||
? props.sceneQueue.makeLink(props.scene.id, { sceneIndex: props.index })
|
||||
: `/scenes/${props.scene.id}`;
|
||||
} else if (props.sceneMarker) {
|
||||
linkSrc = NavUtils.makeSceneMarkerUrl(props.sceneMarker);
|
||||
} else if (props.image) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { SceneQueue } from "src/models/sceneQueue";
|
||||
import { WallItem } from "./WallItem";
|
||||
|
||||
interface IWallPanelProps {
|
||||
scenes?: GQL.SlimSceneDataFragment[];
|
||||
sceneQueue?: SceneQueue;
|
||||
sceneMarkers?: GQL.SceneMarkerDataFragment[];
|
||||
images?: GQL.SlimImageDataFragment[];
|
||||
clickHandler?: (
|
||||
@@ -43,7 +45,9 @@ export const WallPanel: React.FC<IWallPanelProps> = (
|
||||
const scenes = (props.scenes ?? []).map((scene, index, sceneArray) => (
|
||||
<WallItem
|
||||
key={scene.id}
|
||||
index={index}
|
||||
scene={scene}
|
||||
sceneQueue={props.sceneQueue}
|
||||
clickHandler={props.clickHandler}
|
||||
className={calculateClass(index, sceneArray.length)}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"developmentVersion": "Development Version",
|
||||
"images": "Images",
|
||||
"images-size": "Images size",
|
||||
"galleries": "Galleries",
|
||||
"library-size": "Library size",
|
||||
"markers": "Markers",
|
||||
@@ -9,6 +10,7 @@
|
||||
"organized": "Organised",
|
||||
"performers": "Performers",
|
||||
"scenes": "Scenes",
|
||||
"scenes-size": "Scenes size",
|
||||
"studios": "Studios",
|
||||
"tags": "Tags",
|
||||
"up-dir": "Up a directory",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"developmentVersion": "Development Version",
|
||||
"images": "Images",
|
||||
"images-size": "Images size",
|
||||
"galleries": "Galleries",
|
||||
"library-size": "Library size",
|
||||
"markers": "Markers",
|
||||
@@ -9,6 +10,7 @@
|
||||
"organized": "Organized",
|
||||
"performers": "Performers",
|
||||
"scenes": "Scenes",
|
||||
"scenes-size": "Scenes size",
|
||||
"studios": "Studios",
|
||||
"tags": "Tags",
|
||||
"up-dir": "Up a directory",
|
||||
|
||||
@@ -13,6 +13,7 @@ interface IQueryParameters {
|
||||
}
|
||||
|
||||
export interface IPlaySceneOptions {
|
||||
sceneIndex?: number;
|
||||
newPage?: number;
|
||||
autoPlay?: boolean;
|
||||
}
|
||||
@@ -20,11 +21,10 @@ export interface IPlaySceneOptions {
|
||||
export class SceneQueue {
|
||||
public query?: ListFilterModel;
|
||||
public sceneIDs?: number[];
|
||||
private originalQueryPage?: number;
|
||||
private originalQueryPageSize?: number;
|
||||
|
||||
public static fromListFilterModel(
|
||||
filter: ListFilterModel,
|
||||
currentSceneIndex?: number
|
||||
) {
|
||||
public static fromListFilterModel(filter: ListFilterModel) {
|
||||
const ret = new SceneQueue();
|
||||
|
||||
const filterCopy = Object.assign(
|
||||
@@ -33,13 +33,8 @@ export class SceneQueue {
|
||||
);
|
||||
filterCopy.itemsPerPage = 40;
|
||||
|
||||
// adjust page to be correct for the index
|
||||
const filterIndex =
|
||||
currentSceneIndex !== undefined
|
||||
? currentSceneIndex + (filter.currentPage - 1) * filter.itemsPerPage
|
||||
: 0;
|
||||
const newPage = Math.floor(filterIndex / filterCopy.itemsPerPage) + 1;
|
||||
filterCopy.currentPage = newPage;
|
||||
ret.originalQueryPage = filter.currentPage;
|
||||
ret.originalQueryPageSize = filter.itemsPerPage;
|
||||
|
||||
ret.query = filterCopy;
|
||||
return ret;
|
||||
@@ -51,7 +46,7 @@ export class SceneQueue {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private makeQueryParameters(page?: number) {
|
||||
private makeQueryParameters(sceneIndex?: number, page?: number) {
|
||||
if (this.query) {
|
||||
const queryParams = this.query.getQueryParameters();
|
||||
const translatedParams = {
|
||||
@@ -64,6 +59,17 @@ export class SceneQueue {
|
||||
|
||||
if (page !== undefined) {
|
||||
translatedParams.qfp = page;
|
||||
} else if (
|
||||
sceneIndex !== undefined &&
|
||||
this.originalQueryPage !== undefined &&
|
||||
this.originalQueryPageSize !== undefined
|
||||
) {
|
||||
// adjust page to be correct for the index
|
||||
const filterIndex =
|
||||
sceneIndex +
|
||||
(this.originalQueryPage - 1) * this.originalQueryPageSize;
|
||||
const newPage = Math.floor(filterIndex / this.query.itemsPerPage) + 1;
|
||||
translatedParams.qfp = newPage;
|
||||
}
|
||||
|
||||
return queryString.stringify(translatedParams, { encode: false });
|
||||
@@ -109,8 +115,15 @@ export class SceneQueue {
|
||||
sceneID: string,
|
||||
options?: IPlaySceneOptions
|
||||
) {
|
||||
const paramStr = this.makeQueryParameters(options?.newPage);
|
||||
history.push(this.makeLink(sceneID, options));
|
||||
}
|
||||
|
||||
public makeLink(sceneID: string, options?: IPlaySceneOptions) {
|
||||
const paramStr = this.makeQueryParameters(
|
||||
options?.sceneIndex,
|
||||
options?.newPage
|
||||
);
|
||||
const autoplayParam = options?.autoPlay ? "&autoplay=true" : "";
|
||||
history.push(`/scenes/${sceneID}?${paramStr}${autoplayParam}`);
|
||||
return `/scenes/${sceneID}?${paramStr}${autoplayParam}`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user