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:
WithoutPants
2021-04-28 09:27:47 +10:00
committed by GitHub
parent fe0c5615a6
commit 210feb4034
10 changed files with 134 additions and 95 deletions

View File

@@ -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}

View File

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

View File

@@ -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} />;
}
}

View File

@@ -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">

View File

@@ -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,

View File

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

View File

@@ -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)}
/>

View File

@@ -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",

View File

@@ -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",

View File

@@ -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}`;
}
}