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
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. * Filters for performers and studios only supports one item, even though it's a multi select.
TODO: TODO:
* List view for scenes / performers
* Websocket connection to display logs in the UI * Websocket connection to display logs in the UI
`} `}
</pre> </pre>

View File

@@ -7,6 +7,7 @@ import { IBaseProps } from "../../models/base-props";
import { ListFilterModel } from "../../models/list-filter/filter"; import { ListFilterModel } from "../../models/list-filter/filter";
import { DisplayMode, FilterMode } from "../../models/list-filter/types"; import { DisplayMode, FilterMode } from "../../models/list-filter/types";
import { PerformerCard } from "./PerformerCard"; import { PerformerCard } from "./PerformerCard";
import { PerformerListTable } from "./PerformerListTable";
interface IPerformerListProps extends IBaseProps {} interface IPerformerListProps extends IBaseProps {}
@@ -27,7 +28,7 @@ export const PerformerList: FunctionComponent<IPerformerListProps> = (props: IPe
</div> </div>
); );
} else if (filter.displayMode === DisplayMode.List) { } else if (filter.displayMode === DisplayMode.List) {
return <h1>TODO</h1>; return <PerformerListTable performers={result.data.findPerformers.performers}/>;
} else if (filter.displayMode === DisplayMode.Wall) { } else if (filter.displayMode === DisplayMode.Wall) {
return; 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 { import {
H4,
HTMLTable, HTMLTable,
H5, H5,
H6, H6,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import React, { FunctionComponent } from "react"; import React, { FunctionComponent } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import * as GQL from "../../core/generated-graphql"; import * as GQL from "../../core/generated-graphql";
import { TextUtils } from "../../utils/text"; import { TextUtils } from "../../utils/text";
import { TagLink } from "../Shared/TagLink";
import { NavigationUtils } from "../../utils/navigation"; import { NavigationUtils } from "../../utils/navigation";
interface ISceneListTableProps { interface ISceneListTableProps {
scenes: GQL.SlimSceneDataFragment[]; 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[]) { function renderDuration(scene : GQL.SlimSceneDataFragment) {
return tags.map((tag) => ( if (scene.file.duration === undefined) { return; }
<Link to={NavigationUtils.makeTagScenesUrl(tag)}> return TextUtils.secondsToTimestamp(scene.file.duration);
<H6>{tag.name}</H6> }
</Link>
));
}
function renderPerformers(performers : GQL.SlimSceneDataPerformers[]) { function renderTags(tags : GQL.SlimSceneDataTags[]) {
return performers.map((performer) => ( return tags.map((tag) => (
<Link to={NavigationUtils.makePerformerScenesUrl(performer)}> <Link to={NavigationUtils.makeTagScenesUrl(tag)}>
<H6>{performer.name}</H6> <H6>{tag.name}</H6>
</Link> </Link>
)); ));
} }
function renderStudio(studio : GQL.SlimSceneDataStudio | undefined) { function renderPerformers(performers : GQL.SlimSceneDataPerformers[]) {
if (!!studio) { return performers.map((performer) => (
return ( <Link to={NavigationUtils.makePerformerScenesUrl(performer)}>
<Link to={NavigationUtils.makeStudioScenesUrl(studio)}> <H6>{performer.name}</H6>
<H6>{studio.name}</H6> </Link>
</Link> ));
); }
}
}
function renderSceneRow(scene : GQL.SlimSceneDataFragment) { function renderStudio(studio : GQL.SlimSceneDataStudio | undefined) {
if (!!studio) {
return ( return (
<> <Link to={NavigationUtils.makeStudioScenesUrl(studio)}>
<tr> <H6>{studio.name}</H6>
<td> </Link>
<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>
</>
)
} }
}
function renderSceneRow(scene : GQL.SlimSceneDataFragment) {
return ( return (
<> <>
<div className="grid"> <tr>
<HTMLTable> <td>
<thead> {renderSceneImage(scene)}
<tr> </td>
<th>Title</th> <td style={{textAlign: "left"}}>
<th>Rating</th> <Link to={`/scenes/${scene.id}`}>
<th>Duration</th> <H5 style={{textOverflow: "ellipsis", overflow: "hidden"}}>
<th>Tags</th> {!!scene.title ? scene.title : TextUtils.fileNameFromPath(scene.path)}
<th>Performers</th> </H5>
<th>Studio</th> </Link>
</tr> </td>
</thead> <td>
<tbody> {scene.rating ? scene.rating : ''}
{props.scenes.map(renderSceneRow)} </td>
</tbody> <td>
</HTMLTable> {renderDuration(scene)}
</div> </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; padding: 0;
margin: 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 { .grid-item {