mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
Improved list view for scenes, galleries and performers (#4368)
Co-authored-by: InfiniteStash <117855276+InfiniteStash@users.noreply.github.com> Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
141
ui/v2.5/src/components/List/ListTable.tsx
Normal file
141
ui/v2.5/src/components/List/ListTable.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { Table, Form } from "react-bootstrap";
|
||||
import { CheckBoxSelect } from "../Shared/Select";
|
||||
import cx from "classnames";
|
||||
|
||||
export interface IColumn {
|
||||
label: string;
|
||||
value: string;
|
||||
mandatory?: boolean;
|
||||
}
|
||||
|
||||
export const ColumnSelector: React.FC<{
|
||||
selected: string[];
|
||||
allColumns: IColumn[];
|
||||
setSelected: (selected: string[]) => void;
|
||||
}> = ({ selected, allColumns, setSelected }) => {
|
||||
const disableOptions = useMemo(() => {
|
||||
return allColumns.map((col) => {
|
||||
return {
|
||||
...col,
|
||||
disabled: col.mandatory,
|
||||
};
|
||||
});
|
||||
}, [allColumns]);
|
||||
|
||||
const selectedColumns = useMemo(() => {
|
||||
return disableOptions.filter((col) => selected.includes(col.value));
|
||||
}, [selected, disableOptions]);
|
||||
|
||||
return (
|
||||
<CheckBoxSelect
|
||||
options={disableOptions}
|
||||
selectedOptions={selectedColumns}
|
||||
onChange={(v) => {
|
||||
setSelected(v.map((col) => col.value));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IListTableProps<T> {
|
||||
className?: string;
|
||||
items: T[];
|
||||
columns: string[];
|
||||
setColumns: (columns: string[]) => void;
|
||||
allColumns: IColumn[];
|
||||
selectedIds: Set<string>;
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
||||
renderCell: (column: IColumn, item: T, index: number) => React.ReactNode;
|
||||
}
|
||||
|
||||
export const ListTable = <T extends { id: string }>(
|
||||
props: IListTableProps<T>
|
||||
) => {
|
||||
const {
|
||||
className,
|
||||
items,
|
||||
columns,
|
||||
setColumns,
|
||||
allColumns,
|
||||
selectedIds,
|
||||
onSelectChange,
|
||||
renderCell,
|
||||
} = props;
|
||||
|
||||
const visibleColumns = useMemo(() => {
|
||||
return allColumns.filter(
|
||||
(col) => col.mandatory || columns.includes(col.value)
|
||||
);
|
||||
}, [columns, allColumns]);
|
||||
|
||||
const renderObjectRow = (item: T, index: number) => {
|
||||
let shiftKey = false;
|
||||
|
||||
return (
|
||||
<tr key={item.id}>
|
||||
<td className="select-col">
|
||||
<label>
|
||||
<Form.Control
|
||||
type="checkbox"
|
||||
checked={selectedIds.has(item.id)}
|
||||
onChange={() =>
|
||||
onSelectChange(item.id, !selectedIds.has(item.id), shiftKey)
|
||||
}
|
||||
onClick={(
|
||||
event: React.MouseEvent<HTMLInputElement, MouseEvent>
|
||||
) => {
|
||||
shiftKey = event.shiftKey;
|
||||
event.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</td>
|
||||
|
||||
{visibleColumns.map((column) => (
|
||||
<td key={column.value} className={`${column.value}-data`}>
|
||||
{renderCell(column, item, index)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
const columnHeaders = useMemo(() => {
|
||||
return visibleColumns.map((column) => (
|
||||
<th key={column.value} className={`${column.value}-head`}>
|
||||
{column.label}
|
||||
</th>
|
||||
));
|
||||
}, [visibleColumns]);
|
||||
|
||||
return (
|
||||
<div className={cx("table-list", className)}>
|
||||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="select-col">
|
||||
<div
|
||||
className="d-inline-block"
|
||||
data-toggle="popover"
|
||||
data-trigger="focus"
|
||||
>
|
||||
<ColumnSelector
|
||||
allColumns={allColumns}
|
||||
selected={columns}
|
||||
setSelected={setColumns}
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
{columnHeaders}
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="border-row" colSpan={100}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{items.map(renderObjectRow)}</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -370,3 +370,138 @@ input[type="range"].zoom-slider {
|
||||
.tilted {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.table-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
margin-bottom: 1rem;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
max-height: 78dvh;
|
||||
min-width: min-content;
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
|
||||
table {
|
||||
margin: 0;
|
||||
|
||||
thead {
|
||||
background-color: #202b33;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
|
||||
.column-select {
|
||||
margin: 0;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
.comma-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 4px 2px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 190px;
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
li::after {
|
||||
content: ", ";
|
||||
}
|
||||
|
||||
li:last-child::after {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.comma-list:hover {
|
||||
background: #28343c;
|
||||
border: 1px solid #414c53;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.28);
|
||||
display: block;
|
||||
height: auto;
|
||||
margin-left: -0.4rem;
|
||||
margin-top: -0.9rem;
|
||||
max-width: 40rem;
|
||||
overflow: hidden;
|
||||
padding: 0.1rem 0.5rem;
|
||||
position: absolute;
|
||||
top: auto;
|
||||
white-space: normal;
|
||||
width: max-content;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.comma-list li .ellips-data:hover {
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
td {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
|
||||
.ellips-data {
|
||||
display: block;
|
||||
max-width: 190px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.star-rating-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
td.select-col {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
border: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tr {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.date-head {
|
||||
width: 97px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-list tbody tr:hover {
|
||||
background-color: #2d3942;
|
||||
}
|
||||
|
||||
.table-list a {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
.table-list .table-striped td,
|
||||
.table-list .table-striped th {
|
||||
font-size: 1rem;
|
||||
vertical-align: middle;
|
||||
|
||||
h5,
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user