mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
WallPanel refactor (#3686)
This commit is contained in:
@@ -3,7 +3,7 @@ import { Button } from "react-bootstrap";
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { WallPanel } from "src/components/Wall/WallPanel";
|
import { MarkerWallPanel } from "src/components/Wall/WallPanel";
|
||||||
import { PrimaryTags } from "./PrimaryTags";
|
import { PrimaryTags } from "./PrimaryTags";
|
||||||
import { SceneMarkerForm } from "./SceneMarkerForm";
|
import { SceneMarkerForm } from "./SceneMarkerForm";
|
||||||
|
|
||||||
@@ -77,11 +77,12 @@ export const SceneMarkersPanel: React.FC<ISceneMarkersPanelProps> = (
|
|||||||
onEdit={onOpenEditor}
|
onEdit={onOpenEditor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<WallPanel
|
<MarkerWallPanel
|
||||||
sceneMarkers={sceneMarkers}
|
markers={sceneMarkers}
|
||||||
clickHandler={(marker) => {
|
clickHandler={(e, marker) => {
|
||||||
|
e.preventDefault();
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
onClickMarker(marker as GQL.SceneMarkerDataFragment);
|
onClickMarker(marker);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { ListFilterModel } from "src/models/list-filter/filter";
|
|||||||
import { DisplayMode } from "src/models/list-filter/types";
|
import { DisplayMode } from "src/models/list-filter/types";
|
||||||
import { Tagger } from "../Tagger/scenes/SceneTagger";
|
import { Tagger } from "../Tagger/scenes/SceneTagger";
|
||||||
import { IPlaySceneOptions, SceneQueue } from "src/models/sceneQueue";
|
import { IPlaySceneOptions, SceneQueue } from "src/models/sceneQueue";
|
||||||
import { WallPanel } from "../Wall/WallPanel";
|
import { SceneWallPanel } from "../Wall/WallPanel";
|
||||||
import { SceneListTable } from "./SceneListTable";
|
import { SceneListTable } from "./SceneListTable";
|
||||||
import { EditScenesDialog } from "./EditScenesDialog";
|
import { EditScenesDialog } from "./EditScenesDialog";
|
||||||
import { DeleteScenesDialog } from "./DeleteScenesDialog";
|
import { DeleteScenesDialog } from "./DeleteScenesDialog";
|
||||||
@@ -314,7 +314,7 @@ export const SceneList: React.FC<ISceneList> = ({
|
|||||||
}
|
}
|
||||||
if (filter.displayMode === DisplayMode.Wall) {
|
if (filter.displayMode === DisplayMode.Wall) {
|
||||||
return (
|
return (
|
||||||
<WallPanel
|
<SceneWallPanel
|
||||||
scenes={result.data.findScenes.scenes}
|
scenes={result.data.findScenes.scenes}
|
||||||
sceneQueue={queue}
|
sceneQueue={queue}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import NavUtils from "src/utils/navigation";
|
|||||||
import { makeItemList, PersistanceLevel } from "../List/ItemList";
|
import { makeItemList, PersistanceLevel } from "../List/ItemList";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { DisplayMode } from "src/models/list-filter/types";
|
import { DisplayMode } from "src/models/list-filter/types";
|
||||||
import { WallPanel } from "../Wall/WallPanel";
|
import { MarkerWallPanel } from "../Wall/WallPanel";
|
||||||
|
|
||||||
const SceneMarkerItemList = makeItemList({
|
const SceneMarkerItemList = makeItemList({
|
||||||
filterMode: GQL.FilterMode.SceneMarkers,
|
filterMode: GQL.FilterMode.SceneMarkers,
|
||||||
@@ -88,7 +88,7 @@ export const SceneMarkerList: React.FC<ISceneMarkerList> = ({
|
|||||||
|
|
||||||
if (filter.displayMode === DisplayMode.Wall) {
|
if (filter.displayMode === DisplayMode.Wall) {
|
||||||
return (
|
return (
|
||||||
<WallPanel sceneMarkers={result.data.findSceneMarkers.scene_markers} />
|
<MarkerWallPanel markers={result.data.findSceneMarkers.scene_markers} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import React, { useRef, useState, useEffect, useMemo } from "react";
|
import React, {
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
MouseEvent,
|
||||||
|
useMemo,
|
||||||
|
} from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
@@ -9,18 +16,20 @@ import { ConfigurationContext } from "src/hooks/Config";
|
|||||||
import { markerTitle } from "src/core/markers";
|
import { markerTitle } from "src/core/markers";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
|
|
||||||
interface IWallItemProps {
|
export type WallItemType = keyof WallItemData;
|
||||||
|
|
||||||
|
export type WallItemData = {
|
||||||
|
scene: GQL.SlimSceneDataFragment;
|
||||||
|
sceneMarker: GQL.SceneMarkerDataFragment;
|
||||||
|
image: GQL.SlimImageDataFragment;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IWallItemProps<T extends WallItemType> {
|
||||||
|
type: T;
|
||||||
index?: number;
|
index?: number;
|
||||||
scene?: GQL.SlimSceneDataFragment;
|
data: WallItemData[T];
|
||||||
sceneQueue?: SceneQueue;
|
sceneQueue?: SceneQueue;
|
||||||
sceneMarker?: GQL.SceneMarkerDataFragment;
|
clickHandler?: (e: MouseEvent, item: WallItemData[T]) => void;
|
||||||
image?: GQL.SlimImageDataFragment;
|
|
||||||
clickHandler?: (
|
|
||||||
item:
|
|
||||||
| GQL.SlimSceneDataFragment
|
|
||||||
| GQL.SceneMarkerDataFragment
|
|
||||||
| GQL.SlimImageDataFragment
|
|
||||||
) => void;
|
|
||||||
className: string;
|
className: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,26 +40,29 @@ interface IPreviews {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Preview: React.FC<{
|
const Preview: React.FC<{
|
||||||
previews?: IPreviews;
|
previews: IPreviews;
|
||||||
config?: GQL.ConfigDataFragment;
|
config?: GQL.ConfigDataFragment;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
}> = ({ previews, config, active }) => {
|
}> = ({ previews, config, active }) => {
|
||||||
const videoElement = useRef() as React.MutableRefObject<HTMLVideoElement>;
|
const videoEl = useRef<HTMLVideoElement>(null);
|
||||||
const [isMissing, setIsMissing] = useState(false);
|
const [isMissing, setIsMissing] = useState(false);
|
||||||
|
|
||||||
const previewType = config?.interface?.wallPlayback;
|
const previewType = config?.interface?.wallPlayback;
|
||||||
const soundOnPreview = config?.interface?.soundOnPreview ?? false;
|
const soundOnPreview = config?.interface?.soundOnPreview ?? false;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!videoElement.current) return;
|
const video = videoEl.current;
|
||||||
videoElement.current.muted = !(soundOnPreview && active);
|
if (!video) return;
|
||||||
if (previewType !== "video") {
|
|
||||||
if (active) videoElement.current.play();
|
|
||||||
else videoElement.current.pause();
|
|
||||||
}
|
|
||||||
}, [videoElement, previewType, soundOnPreview, active]);
|
|
||||||
|
|
||||||
if (!previews) return <div />;
|
video.muted = !(soundOnPreview && active);
|
||||||
|
if (previewType !== "video") {
|
||||||
|
if (active) {
|
||||||
|
video.play();
|
||||||
|
} else {
|
||||||
|
video.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [previewType, soundOnPreview, active]);
|
||||||
|
|
||||||
const image = (
|
const image = (
|
||||||
<img
|
<img
|
||||||
@@ -77,7 +89,7 @@ const Preview: React.FC<{
|
|||||||
// Error code 4 indicates media not found or unsupported
|
// Error code 4 indicates media not found or unsupported
|
||||||
setIsMissing(error.currentTarget.error?.code === 4);
|
setIsMissing(error.currentTarget.error?.code === 4);
|
||||||
}}
|
}}
|
||||||
ref={videoElement}
|
ref={videoEl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -105,108 +117,123 @@ const Preview: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
export const WallItem = <T extends WallItemType>({
|
||||||
|
type,
|
||||||
|
index,
|
||||||
|
data,
|
||||||
|
sceneQueue,
|
||||||
|
clickHandler,
|
||||||
|
className,
|
||||||
|
}: IWallItemProps<T>) => {
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
const wallItem = useRef() as React.MutableRefObject<HTMLDivElement>;
|
const itemEl = useRef<HTMLDivElement>(null);
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = React.useContext(ConfigurationContext);
|
||||||
|
|
||||||
const showTextContainer = config?.interface.wallShowTitle ?? true;
|
const showTextContainer = config?.interface.wallShowTitle ?? true;
|
||||||
|
|
||||||
const previews = props.sceneMarker
|
const previews = useMemo(() => {
|
||||||
? {
|
switch (type) {
|
||||||
video: props.sceneMarker.stream,
|
case "scene":
|
||||||
animation: props.sceneMarker.preview,
|
const scene = data as GQL.SlimSceneDataFragment;
|
||||||
image: props.sceneMarker.screenshot,
|
return {
|
||||||
}
|
video: scene.paths.preview ?? undefined,
|
||||||
: props.scene
|
animation: scene.paths.webp ?? undefined,
|
||||||
? {
|
image: scene.paths.screenshot ?? undefined,
|
||||||
video: props.scene?.paths.preview ?? undefined,
|
};
|
||||||
animation: props.scene?.paths.webp ?? undefined,
|
case "sceneMarker":
|
||||||
image: props.scene?.paths.screenshot ?? undefined,
|
const sceneMarker = data as GQL.SceneMarkerDataFragment;
|
||||||
}
|
return {
|
||||||
: props.image
|
video: sceneMarker.stream,
|
||||||
? {
|
animation: sceneMarker.preview,
|
||||||
image: props.image?.paths.thumbnail ?? undefined,
|
image: sceneMarker.screenshot,
|
||||||
}
|
};
|
||||||
: undefined;
|
case "image":
|
||||||
|
const image = data as GQL.SlimImageDataFragment;
|
||||||
|
return {
|
||||||
|
image: image.paths.thumbnail ?? undefined,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
// this is unreachable, inference fails for some reason
|
||||||
|
return type as never;
|
||||||
|
}
|
||||||
|
}, [type, data]);
|
||||||
|
const linkSrc = useMemo(() => {
|
||||||
|
switch (type) {
|
||||||
|
case "scene":
|
||||||
|
const scene = data as GQL.SlimSceneDataFragment;
|
||||||
|
return sceneQueue
|
||||||
|
? sceneQueue.makeLink(scene.id, { sceneIndex: index })
|
||||||
|
: `/scenes/${scene.id}`;
|
||||||
|
case "sceneMarker":
|
||||||
|
const sceneMarker = data as GQL.SceneMarkerDataFragment;
|
||||||
|
return NavUtils.makeSceneMarkerUrl(sceneMarker);
|
||||||
|
case "image":
|
||||||
|
const image = data as GQL.SlimImageDataFragment;
|
||||||
|
return `/images/${image.id}`;
|
||||||
|
default:
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}, [type, data, sceneQueue, index]);
|
||||||
|
const title = useMemo(() => {
|
||||||
|
switch (type) {
|
||||||
|
case "scene":
|
||||||
|
const scene = data as GQL.SlimSceneDataFragment;
|
||||||
|
return objectTitle(scene);
|
||||||
|
case "sceneMarker":
|
||||||
|
const sceneMarker = data as GQL.SceneMarkerDataFragment;
|
||||||
|
const newTitle = markerTitle(sceneMarker);
|
||||||
|
const seconds = TextUtils.secondsToTimestamp(sceneMarker.seconds);
|
||||||
|
if (newTitle) {
|
||||||
|
return `${newTitle} - ${seconds}`;
|
||||||
|
} else {
|
||||||
|
return seconds;
|
||||||
|
}
|
||||||
|
case "image":
|
||||||
|
return "";
|
||||||
|
default:
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}, [type, data]);
|
||||||
|
const tags = useMemo(() => {
|
||||||
|
if (type === "sceneMarker") {
|
||||||
|
const sceneMarker = data as GQL.SceneMarkerDataFragment;
|
||||||
|
return [sceneMarker.primary_tag, ...sceneMarker.tags];
|
||||||
|
}
|
||||||
|
}, [type, data]);
|
||||||
|
|
||||||
const setInactive = () => setActive(false);
|
const setInactive = () => setActive(false);
|
||||||
const toggleActive = (e: TransitionEvent) => {
|
const toggleActive = useCallback((e: TransitionEvent) => {
|
||||||
if (e.propertyName === "transform" && e.elapsedTime === 0) {
|
if (e.propertyName === "transform" && e.elapsedTime === 0) {
|
||||||
// Get the current scale of the wall-item. If it's smaller than 1.1 the item is being scaled up, otherwise down.
|
// Get the current scale of the wall-item. If it's smaller than 1.1 the item is being scaled up, otherwise down.
|
||||||
const matrixScale = getComputedStyle(wallItem.current).transform.match(
|
const matrixScale = getComputedStyle(itemEl.current!).transform.match(
|
||||||
/-?\d+\.?\d+|\d+/g
|
/-?\d+\.?\d+|\d+/g
|
||||||
)?.[0];
|
)?.[0];
|
||||||
const scale = Number.parseFloat(matrixScale ?? "2") || 2;
|
const scale = Number.parseFloat(matrixScale ?? "2") || 2;
|
||||||
setActive(scale <= 1.1 && !active);
|
setActive((value) => scale <= 1.1 && !value);
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { current } = wallItem;
|
const item = itemEl.current!;
|
||||||
current?.addEventListener("transitioncancel", setInactive);
|
item.addEventListener("transitioncancel", setInactive);
|
||||||
current?.addEventListener("transitionstart", toggleActive);
|
item.addEventListener("transitionstart", toggleActive);
|
||||||
return () => {
|
return () => {
|
||||||
current?.removeEventListener("transitioncancel", setInactive);
|
item.removeEventListener("transitioncancel", setInactive);
|
||||||
current?.removeEventListener("transitionstart", toggleActive);
|
item.removeEventListener("transitionstart", toggleActive);
|
||||||
};
|
};
|
||||||
});
|
}, [toggleActive]);
|
||||||
|
|
||||||
const clickHandler = () => {
|
const onClick = (e: MouseEvent) => {
|
||||||
if (props.scene) {
|
clickHandler?.(e, data);
|
||||||
props?.clickHandler?.(props.scene);
|
|
||||||
}
|
|
||||||
if (props.sceneMarker) {
|
|
||||||
props?.clickHandler?.(props.sceneMarker);
|
|
||||||
}
|
|
||||||
if (props.image) {
|
|
||||||
props?.clickHandler?.(props.image);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
|
||||||
continue: cont,
|
|
||||||
})
|
|
||||||
: `/scenes/${props.scene.id}`;
|
|
||||||
} else if (props.sceneMarker) {
|
|
||||||
linkSrc = NavUtils.makeSceneMarkerUrl(props.sceneMarker);
|
|
||||||
} else if (props.image) {
|
|
||||||
linkSrc = `/images/${props.image.id}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = useMemo(() => {
|
|
||||||
if (props.sceneMarker) {
|
|
||||||
return `${markerTitle(
|
|
||||||
props.sceneMarker
|
|
||||||
)} - ${TextUtils.secondsToTimestamp(props.sceneMarker.seconds)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.scene) {
|
|
||||||
return objectTitle(props.scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}, [props.sceneMarker, props.scene]);
|
|
||||||
|
|
||||||
const renderText = () => {
|
const renderText = () => {
|
||||||
if (!showTextContainer) return;
|
if (!showTextContainer) return;
|
||||||
|
|
||||||
const tags = props.sceneMarker
|
|
||||||
? [props.sceneMarker.primary_tag, ...props.sceneMarker.tags]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="wall-item-text">
|
<div className="wall-item-text">
|
||||||
<div>{title}</div>
|
<div>{title}</div>
|
||||||
{tags.map((tag) => (
|
{tags?.map((tag) => (
|
||||||
<span key={tag.id} className="wall-tag">
|
<span key={tag.id} className="wall-tag">
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</span>
|
</span>
|
||||||
@@ -217,8 +244,8 @@ export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="wall-item">
|
<div className="wall-item">
|
||||||
<div className={`wall-item-container ${props.className}`} ref={wallItem}>
|
<div className={`wall-item-container ${className}`} ref={itemEl}>
|
||||||
<Link onClick={clickHandler} to={linkSrc} className="wall-item-anchor">
|
<Link onClick={onClick} to={linkSrc} className="wall-item-anchor">
|
||||||
<Preview previews={previews} config={config} active={active} />
|
<Preview previews={previews} config={config} active={active} />
|
||||||
{renderText()}
|
{renderText()}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
import React from "react";
|
import React, { MouseEvent } from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
import { WallItem } from "./WallItem";
|
import { WallItem, WallItemData, WallItemType } from "./WallItem";
|
||||||
|
|
||||||
interface IWallPanelProps {
|
interface IWallPanelProps<T extends WallItemType> {
|
||||||
scenes?: GQL.SlimSceneDataFragment[];
|
type: T;
|
||||||
|
data: WallItemData[T][];
|
||||||
sceneQueue?: SceneQueue;
|
sceneQueue?: SceneQueue;
|
||||||
sceneMarkers?: GQL.SceneMarkerDataFragment[];
|
clickHandler?: (e: MouseEvent, item: WallItemData[T]) => void;
|
||||||
images?: GQL.SlimImageDataFragment[];
|
|
||||||
clickHandler?: (
|
|
||||||
item:
|
|
||||||
| GQL.SlimSceneDataFragment
|
|
||||||
| GQL.SceneMarkerDataFragment
|
|
||||||
| GQL.SlimImageDataFragment
|
|
||||||
) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculateClass = (index: number, count: number) => {
|
const calculateClass = (index: number, count: number) => {
|
||||||
@@ -33,53 +27,84 @@ const calculateClass = (index: number, count: number) => {
|
|||||||
if (index % 5 === 4) return "transform-origin-right";
|
if (index % 5 === 4) return "transform-origin-right";
|
||||||
// Multiple of five
|
// Multiple of five
|
||||||
if (index % 5 === 0) return "transform-origin-left";
|
if (index % 5 === 0) return "transform-origin-left";
|
||||||
// Position is equal or larger than first postion in last row
|
// Position is equal or larger than first position in last row
|
||||||
if (count - (count % 5 || 5) <= index + 1) return "transform-origin-bottom";
|
if (count - (count % 5 || 5) <= index + 1) return "transform-origin-bottom";
|
||||||
// Default
|
// Default
|
||||||
return "transform-origin-center";
|
return "transform-origin-center";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WallPanel: React.FC<IWallPanelProps> = (
|
const WallPanel = <T extends WallItemType>({
|
||||||
props: IWallPanelProps
|
type,
|
||||||
) => {
|
data,
|
||||||
const scenes = (props.scenes ?? []).map((scene, index, sceneArray) => (
|
sceneQueue,
|
||||||
<WallItem
|
clickHandler,
|
||||||
key={scene.id}
|
}: IWallPanelProps<T>) => {
|
||||||
index={index}
|
function renderItems() {
|
||||||
scene={scene}
|
return data.map((item, index, arr) => (
|
||||||
sceneQueue={props.sceneQueue}
|
|
||||||
clickHandler={props.clickHandler}
|
|
||||||
className={calculateClass(index, sceneArray.length)}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
const sceneMarkers = (props.sceneMarkers ?? []).map(
|
|
||||||
(marker, index, markerArray) => (
|
|
||||||
<WallItem
|
<WallItem
|
||||||
key={marker.id}
|
type={type}
|
||||||
sceneMarker={marker}
|
key={item.id}
|
||||||
clickHandler={props.clickHandler}
|
index={index}
|
||||||
className={calculateClass(index, markerArray.length)}
|
data={item}
|
||||||
|
sceneQueue={sceneQueue}
|
||||||
|
clickHandler={clickHandler}
|
||||||
|
className={calculateClass(index, arr.length)}
|
||||||
/>
|
/>
|
||||||
)
|
));
|
||||||
);
|
}
|
||||||
|
|
||||||
const images = (props.images ?? []).map((image, index, imageArray) => (
|
|
||||||
<WallItem
|
|
||||||
key={image.id}
|
|
||||||
image={image}
|
|
||||||
clickHandler={props.clickHandler}
|
|
||||||
className={calculateClass(index, imageArray.length)}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="wall w-100 row justify-content-center">
|
<div className="wall w-100 row justify-content-center">
|
||||||
{scenes}
|
{renderItems()}
|
||||||
{sceneMarkers}
|
|
||||||
{images}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IImageWallPanelProps {
|
||||||
|
images: GQL.SlimImageDataFragment[];
|
||||||
|
clickHandler?: (e: MouseEvent, item: GQL.SlimImageDataFragment) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImageWallPanel: React.FC<IImageWallPanelProps> = ({
|
||||||
|
images,
|
||||||
|
clickHandler,
|
||||||
|
}) => {
|
||||||
|
return <WallPanel type="image" data={images} clickHandler={clickHandler} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IMarkerWallPanelProps {
|
||||||
|
markers: GQL.SceneMarkerDataFragment[];
|
||||||
|
clickHandler?: (e: MouseEvent, item: GQL.SceneMarkerDataFragment) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MarkerWallPanel: React.FC<IMarkerWallPanelProps> = ({
|
||||||
|
markers,
|
||||||
|
clickHandler,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<WallPanel type="sceneMarker" data={markers} clickHandler={clickHandler} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ISceneWallPanelProps {
|
||||||
|
scenes: GQL.SlimSceneDataFragment[];
|
||||||
|
sceneQueue?: SceneQueue;
|
||||||
|
clickHandler?: (e: MouseEvent, item: GQL.SlimSceneDataFragment) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SceneWallPanel: React.FC<ISceneWallPanelProps> = ({
|
||||||
|
scenes,
|
||||||
|
sceneQueue,
|
||||||
|
clickHandler,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<WallPanel
|
||||||
|
type="scene"
|
||||||
|
data={scenes}
|
||||||
|
sceneQueue={sceneQueue}
|
||||||
|
clickHandler={clickHandler}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user