Merge pull request #142 from WithoutPants/performer_list_view

Add performer list view.
This commit is contained in:
Leopere 2019-10-15 20:03:24 -04:00 committed by GitHub
commit 0a8e272c97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 234 additions and 88 deletions

View file

@ -57,7 +57,6 @@ export const Stats: FunctionComponent = () => {
* Filters for performers and studios only supports one item, even though it's a multi select.
TODO:
* List view for scenes / performers
* Websocket connection to display logs in the UI
`}
</pre>

View file

@ -7,6 +7,7 @@ import { IBaseProps } from "../../models/base-props";
import { ListFilterModel } from "../../models/list-filter/filter";
import { DisplayMode, FilterMode } from "../../models/list-filter/types";
import { PerformerCard } from "./PerformerCard";
import { PerformerListTable } from "./PerformerListTable";
interface IPerformerListProps extends IBaseProps {}
@ -27,7 +28,7 @@ export const PerformerList: FunctionComponent<IPerformerListProps> = (props: IPe
</div>
);
} else if (filter.displayMode === DisplayMode.List) {
return <h1>TODO</h1>;
return <PerformerListTable performers={result.data.findPerformers.performers}/>;
} else if (filter.displayMode === DisplayMode.Wall) {
return;
}

View file

@ -0,0 +1,107 @@
import {
HTMLTable,
H5,
H6,
Button,
} from "@blueprintjs/core";
import React, { FunctionComponent } from "react";
import { Link } from "react-router-dom";
import * as GQL from "../../core/generated-graphql";
import { NavigationUtils } from "../../utils/navigation";
interface IPerformerListTableProps {
performers: GQL.PerformerDataFragment[];
}
export const PerformerListTable: FunctionComponent<IPerformerListTableProps> = (props: IPerformerListTableProps) => {
function maybeRenderFavoriteHeart(performer : GQL.PerformerDataFragment) {
if (!performer.favorite) { return; }
return (
<Button
icon="heart"
disabled={true}
className="favorite"
minimal={true}
/>
);
}
function renderPerformerImage(performer : GQL.PerformerDataFragment) {
const style: React.CSSProperties = {
backgroundImage: `url('${performer.image_path}')`,
lineHeight: 5,
backgroundSize: "contain",
display: "inline-block",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
};
return (
<Link
className="performer-list-thumbnail"
to={`/performers/${performer.id}`}
style={style}/>
)
}
function renderPerformerRow(performer : GQL.PerformerDataFragment) {
return (
<>
<tr>
<td>
{renderPerformerImage(performer)}
</td>
<td style={{textAlign: "left"}}>
<Link to={`/performers/${performer.id}`}>
<H5 style={{textOverflow: "ellipsis", overflow: "hidden"}}>
{performer.name}
</H5>
</Link>
</td>
<td>
{performer.aliases ? performer.aliases : ''}
</td>
<td>
{maybeRenderFavoriteHeart(performer)}
</td>
<td>
<Link to={NavigationUtils.makePerformerScenesUrl(performer)}>
<H6>{performer.scene_count}</H6>
</Link>
</td>
<td>
{performer.birthdate}
</td>
<td>
{performer.height}
</td>
</tr>
</>
)
}
return (
<>
<div className="grid">
<HTMLTable>
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Aliases</th>
<th>Favourite</th>
<th>Scene Count</th>
<th>Birthdate</th>
<th>Height</th>
</tr>
</thead>
<tbody>
{props.performers.map(renderPerformerRow)}
</tbody>
</HTMLTable>
</div>
</>
);
};

View file

@ -1,104 +1,124 @@
import {
H4,
HTMLTable,
H5,
H6,
} from "@blueprintjs/core";
import React, { FunctionComponent } from "react";
import { Link } from "react-router-dom";
import * as GQL from "../../core/generated-graphql";
import { TextUtils } from "../../utils/text";
import { TagLink } from "../Shared/TagLink";
import React, { FunctionComponent } from "react";
import { Link } from "react-router-dom";
import * as GQL from "../../core/generated-graphql";
import { TextUtils } from "../../utils/text";
import { NavigationUtils } from "../../utils/navigation";
interface ISceneListTableProps {
scenes: GQL.SlimSceneDataFragment[];
interface ISceneListTableProps {
scenes: GQL.SlimSceneDataFragment[];
}
export const SceneListTable: FunctionComponent<ISceneListTableProps> = (props: ISceneListTableProps) => {
function renderSceneImage(scene : GQL.SlimSceneDataFragment) {
const style: React.CSSProperties = {
backgroundImage: `url('${scene.paths.screenshot}')`,
lineHeight: 5,
backgroundSize: "contain",
display: "inline-block",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
};
return (
<Link
className="scene-list-thumbnail"
to={`/performers/${scene.id}`}
style={style}/>
)
}
export const SceneListTable: FunctionComponent<ISceneListTableProps> = (props: ISceneListTableProps) => {
function renderDuration(scene : GQL.SlimSceneDataFragment) {
if (scene.file.duration === undefined) { return; }
return TextUtils.secondsToTimestamp(scene.file.duration);
}
function renderTags(tags : GQL.SlimSceneDataTags[]) {
return tags.map((tag) => (
<Link to={NavigationUtils.makeTagScenesUrl(tag)}>
<H6>{tag.name}</H6>
</Link>
));
}
function renderDuration(scene : GQL.SlimSceneDataFragment) {
if (scene.file.duration === undefined) { return; }
return TextUtils.secondsToTimestamp(scene.file.duration);
}
function renderPerformers(performers : GQL.SlimSceneDataPerformers[]) {
return performers.map((performer) => (
<Link to={NavigationUtils.makePerformerScenesUrl(performer)}>
<H6>{performer.name}</H6>
</Link>
));
}
function renderTags(tags : GQL.SlimSceneDataTags[]) {
return tags.map((tag) => (
<Link to={NavigationUtils.makeTagScenesUrl(tag)}>
<H6>{tag.name}</H6>
</Link>
));
}
function renderStudio(studio : GQL.SlimSceneDataStudio | undefined) {
if (!!studio) {
return (
<Link to={NavigationUtils.makeStudioScenesUrl(studio)}>
<H6>{studio.name}</H6>
</Link>
);
}
}
function renderPerformers(performers : GQL.SlimSceneDataPerformers[]) {
return performers.map((performer) => (
<Link to={NavigationUtils.makePerformerScenesUrl(performer)}>
<H6>{performer.name}</H6>
</Link>
));
}
function renderSceneRow(scene : GQL.SlimSceneDataFragment) {
function renderStudio(studio : GQL.SlimSceneDataStudio | undefined) {
if (!!studio) {
return (
<>
<tr>
<td>
<Link to={`/scenes/${scene.id}`}>
<H5 style={{textOverflow: "ellipsis", overflow: "hidden"}}>
{!!scene.title ? scene.title : TextUtils.fileNameFromPath(scene.path)}
</H5>
</Link>
</td>
<td>
{scene.rating ? scene.rating : ''}
</td>
<td>
{renderDuration(scene)}
</td>
<td>
{renderTags(scene.tags)}
</td>
<td>
{renderPerformers(scene.performers)}
</td>
<td>
{renderStudio(scene.studio)}
</td>
</tr>
</>
)
<Link to={NavigationUtils.makeStudioScenesUrl(studio)}>
<H6>{studio.name}</H6>
</Link>
);
}
}
function renderSceneRow(scene : GQL.SlimSceneDataFragment) {
return (
<>
<div className="grid">
<HTMLTable>
<thead>
<tr>
<th>Title</th>
<th>Rating</th>
<th>Duration</th>
<th>Tags</th>
<th>Performers</th>
<th>Studio</th>
</tr>
</thead>
<tbody>
{props.scenes.map(renderSceneRow)}
</tbody>
</HTMLTable>
</div>
<tr>
<td>
{renderSceneImage(scene)}
</td>
<td style={{textAlign: "left"}}>
<Link to={`/scenes/${scene.id}`}>
<H5 style={{textOverflow: "ellipsis", overflow: "hidden"}}>
{!!scene.title ? scene.title : TextUtils.fileNameFromPath(scene.path)}
</H5>
</Link>
</td>
<td>
{scene.rating ? scene.rating : ''}
</td>
<td>
{renderDuration(scene)}
</td>
<td>
{renderTags(scene.tags)}
</td>
<td>
{renderPerformers(scene.performers)}
</td>
<td>
{renderStudio(scene.studio)}
</td>
</tr>
</>
);
};
)
}
return (
<>
<div className="grid">
<HTMLTable>
<thead>
<tr>
<th></th>
<th>Title</th>
<th>Rating</th>
<th>Duration</th>
<th>Tags</th>
<th>Performers</th>
<th>Studio</th>
</tr>
</thead>
<tbody>
{props.scenes.map(renderSceneRow)}
</tbody>
</HTMLTable>
</div>
</>
);
};

View file

@ -44,6 +44,25 @@ code {
padding: 0;
margin: 0;
}
& .bp3-button.favorite .bp3-icon {
color: #ff7373 !important
}
& .performer-list-thumbnail {
min-width: 50px;
height: 100px;
}
& .scene-list-thumbnail {
width: 150px;
min-height: 50px;
}
& table td {
text-align: center;
vertical-align: middle;
}
}
.grid-item {