mirror of
https://github.com/collinbarrett/FilterLists.git
synced 2026-03-11 09:04:27 +00:00
refactor Home component
This commit is contained in:
parent
691bab9ba6
commit
f742992d74
32 changed files with 315 additions and 275 deletions
|
|
@ -1,26 +1,10 @@
|
|||
import * as React from "react";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import "isomorphic-fetch";
|
||||
import "../../utils/loader.css";
|
||||
import ReactTable from "react-table"
|
||||
import "react-table/react-table.css"
|
||||
import "./home.css";
|
||||
import * as moment from "moment";
|
||||
import { getContrast } from "../../utils/GetContrast";
|
||||
import { DetailsExpanderContainer } from "./components";
|
||||
|
||||
interface IHomeState {
|
||||
lists: IListDto[];
|
||||
loadingLists: boolean;
|
||||
ruleCount: number;
|
||||
loadingRuleCount: boolean;
|
||||
software: ISoftwareDto[];
|
||||
loadingSoftware: boolean;
|
||||
languages: ILanguageDto[];
|
||||
loadingLanguages: boolean;
|
||||
columnVisibility: IColumnVisibility[];
|
||||
pageSize: number;
|
||||
}
|
||||
import { IListDto } from "./IListDto";
|
||||
import { ISoftwareDto } from "./ISoftwareDto";
|
||||
import { ILanguageDto } from "./ILanguageDto";
|
||||
import { Tagline } from "./Tagline";
|
||||
import { ListsTable } from "./ListsTable";
|
||||
import { IColumnVisibility } from "./ListsTable";
|
||||
|
||||
const columnVisibilityDefaults = [
|
||||
{ column: "Software", visible: true },
|
||||
|
|
@ -29,74 +13,42 @@ const columnVisibilityDefaults = [
|
|||
{ column: "Updated Date", visible: true }
|
||||
];
|
||||
|
||||
export class Home extends React.Component<RouteComponentProps<{}>, IHomeState> {
|
||||
interface IProps {
|
||||
lists: IListDto[];
|
||||
ruleCount: number;
|
||||
software: ISoftwareDto[];
|
||||
languages: ILanguageDto[];
|
||||
}
|
||||
|
||||
interface IState {
|
||||
columnVisibility: IColumnVisibility[];
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export class Home extends React.Component<IProps, IState> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
lists: [],
|
||||
loadingLists: true,
|
||||
ruleCount: 0,
|
||||
loadingRuleCount: true,
|
||||
software: [],
|
||||
loadingSoftware: true,
|
||||
languages: [],
|
||||
loadingLanguages: true,
|
||||
columnVisibility: columnVisibilityDefaults,
|
||||
pageSize: 20
|
||||
};
|
||||
this.updatePageSize = this.updatePageSize.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const contents = this.state.loadingLists ||
|
||||
this.state.loadingRuleCount ||
|
||||
this.state.loadingSoftware ||
|
||||
this.state.loadingLanguages
|
||||
? <div className="loader">Loading...</div>
|
||||
: <div>
|
||||
{Home.renderTagline(this.state)}
|
||||
{Home.renderFilterListsTable(this.state)}
|
||||
{this.renderColumnVisibilityCheckboxes()}
|
||||
</div>;
|
||||
return <div>
|
||||
{contents}
|
||||
</div>;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updatePageSize();
|
||||
fetch("https://filterlists.com/api/v1/lists")
|
||||
.then(response => response.json() as Promise<IListDto[]>)
|
||||
.then(data => {
|
||||
this.setState({
|
||||
lists: data,
|
||||
loadingLists: false
|
||||
});
|
||||
});
|
||||
fetch("https://filterlists.com/api/v1/rules")
|
||||
.then(response => response.json() as Promise<number>)
|
||||
.then(data => {
|
||||
this.setState({
|
||||
ruleCount: data,
|
||||
loadingRuleCount: false
|
||||
});
|
||||
});
|
||||
fetch("https://filterlists.com/api/v1/software")
|
||||
.then(response => response.json() as Promise<ISoftwareDto[]>)
|
||||
.then(data => {
|
||||
this.setState({
|
||||
software: data,
|
||||
loadingSoftware: false
|
||||
});
|
||||
});
|
||||
fetch("https://filterlists.com/api/v1/languages")
|
||||
.then(response => response.json() as Promise<ILanguageDto[]>)
|
||||
.then(data => {
|
||||
this.setState({
|
||||
languages: data,
|
||||
loadingLanguages: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.lists
|
||||
? <div>
|
||||
<Tagline {...this.props} listCount={this.props.lists.length}/>
|
||||
<ListsTable {...this.props}
|
||||
pageSize={this.state.pageSize}
|
||||
columnVisibility={this.state.columnVisibility}/>
|
||||
{this.renderColumnVisibilityCheckboxes()}
|
||||
</div>
|
||||
: null;
|
||||
}
|
||||
|
||||
updatePageSize() {
|
||||
|
|
@ -105,168 +57,24 @@ export class Home extends React.Component<RouteComponentProps<{}>, IHomeState> {
|
|||
});
|
||||
}
|
||||
|
||||
private static renderTagline(state: IHomeState) {
|
||||
return <p className="ml-2 mr-2">
|
||||
The independent, comprehensive directory of <strong>{state.ruleCount.toLocaleString()
|
||||
}</strong> unique rules across <strong>{state.lists.length
|
||||
}</strong> filter and host lists for advertisements, trackers, malware, and annoyances.
|
||||
</p>;
|
||||
}
|
||||
|
||||
private static renderFilterListsTable(state: IHomeState) {
|
||||
return <ReactTable
|
||||
data={state.lists}
|
||||
key={state.pageSize}
|
||||
defaultPageSize={state.pageSize}
|
||||
showPageSizeOptions={false}
|
||||
columns={[
|
||||
{
|
||||
Header: "Name",
|
||||
accessor: "name",
|
||||
filterable: true,
|
||||
filterMethod: (filter: any, row: any) => row[filter.id].toUpperCase()
|
||||
.includes(filter.value.toUpperCase()),
|
||||
sortMethod: (a: any, b: any) => a.toUpperCase() > b.toUpperCase() ? 1 : -1,
|
||||
Cell: (cell: any) => <h2 className="mb-0">{cell.value}</h2>,
|
||||
style: { overflow: "visible" }
|
||||
},
|
||||
{
|
||||
Header: "Software Support",
|
||||
accessor: "softwareIds",
|
||||
filterable: true,
|
||||
filterMethod: (filter: any, row: any) => {
|
||||
if (filter.value === "any") {
|
||||
return true;
|
||||
}
|
||||
return row[filter.id].join(",").split(",").includes(filter.value);
|
||||
},
|
||||
Filter: ({ filter, onChange }) =>
|
||||
<select
|
||||
onChange={(event: any) => onChange(event.target.value)}
|
||||
style={{ width: "100%" }}
|
||||
value={filter ? filter.value : "any"}>
|
||||
<option value="any">Any</option>
|
||||
{state.software.map((e: any) => <option value={e.id}>{e.name}</option>)}
|
||||
</select>,
|
||||
sortable: false,
|
||||
Cell: () => null,
|
||||
width: 250,
|
||||
headerClassName: "d-none d-md-block",
|
||||
className: "d-none d-md-block",
|
||||
show: state.columnVisibility.filter((c: IColumnVisibility) => {
|
||||
return c.column === "Software";
|
||||
})[0].visible
|
||||
},
|
||||
{
|
||||
Header: "Tags",
|
||||
accessor: "tags",
|
||||
filterable: true,
|
||||
filterMethod: (filter: any, row: any) => row[filter.id].map((e: any) => e.name).join()
|
||||
.toUpperCase().includes(filter.value.toUpperCase()),
|
||||
sortable: false,
|
||||
Cell: (cell: any) => <div className="fl-tag-container">{cell.value.map(
|
||||
(e: any) => <span className="badge" style={{
|
||||
backgroundColor: `#${e.colorHex}`,
|
||||
color: getContrast(`${e.colorHex}`)
|
||||
}} title={e.description}>{e.name}</span>)}</div>,
|
||||
width: 200,
|
||||
headerClassName: "d-none d-md-block",
|
||||
className: "d-none d-md-block",
|
||||
show: state.columnVisibility.filter((c: IColumnVisibility) => {
|
||||
return c.column === "Tags";
|
||||
})[0].visible
|
||||
},
|
||||
{
|
||||
Header: "Languages",
|
||||
accessor: "languages",
|
||||
filterable: true,
|
||||
filterMethod: (filter: any, row: any) => {
|
||||
if (filter.value === "any") {
|
||||
return true;
|
||||
}
|
||||
return row[filter.id].map((x: any) => x.iso6391).includes(filter.value);
|
||||
},
|
||||
Filter: ({ filter, onChange }) =>
|
||||
<select
|
||||
onChange={(event: any) => onChange(event.target.value)}
|
||||
style={{ width: "100%" }}
|
||||
value={filter ? filter.value : "any"}>
|
||||
<option value="any">Any</option>
|
||||
{state.languages.map((e: any) => <option value={e.iso6391}>{e.name}</option>)}
|
||||
</select>,
|
||||
sortable: false,
|
||||
Cell: (cell: any) => <div className="fl-tag-container">{cell.value.map(
|
||||
(e: any) => <span className="badge badge-secondary" title={e.name}>{e.iso6391}</span>)
|
||||
}</div>,
|
||||
style: { whiteSpace: "inherit" },
|
||||
width: 100,
|
||||
headerClassName: "d-none d-md-block",
|
||||
className: "d-none d-md-block",
|
||||
show: state.columnVisibility.filter((c: IColumnVisibility) => {
|
||||
return c.column === "Languages";
|
||||
})[0].visible
|
||||
},
|
||||
{
|
||||
Header: "Updated",
|
||||
accessor: "updatedDate",
|
||||
filterable: true,
|
||||
filterMethod: (filter: any, row: any) => row[filter.id].includes(filter.value),
|
||||
sortMethod: (a: any, b: any) => moment(a).isValid()
|
||||
? (moment(b).isValid() ? (moment(a).isBefore(b) ? -1 : 1) : 1)
|
||||
: -1,
|
||||
Cell: (cell: any) => <div>{moment(cell.value).isValid()
|
||||
? moment(cell.value).format("l")
|
||||
: "N/A"}</div>,
|
||||
style: { whiteSpace: "inherit" },
|
||||
width: 100,
|
||||
headerClassName: "d-none d-md-block",
|
||||
className: "d-none d-md-block",
|
||||
show: state.columnVisibility.filter((c: IColumnVisibility) => {
|
||||
return c.column === "Updated Date";
|
||||
})[0].visible
|
||||
},
|
||||
{
|
||||
Header: "Details",
|
||||
accessor: "id",
|
||||
sortable: false,
|
||||
expander: true,
|
||||
Expander: ({ isExpanded, row }) =>
|
||||
<div>
|
||||
{isExpanded
|
||||
? <button className="btn btn-primary btn-block active"
|
||||
title={`Collapse details about ${row.name}.`}>
|
||||
Details
|
||||
</button>
|
||||
: <button className="btn btn-primary btn-block"
|
||||
title={`Learn more about ${row.name}.`}>
|
||||
Details
|
||||
</button>}
|
||||
</div>,
|
||||
style: { textAlign: "center" },
|
||||
width: 90
|
||||
}
|
||||
]}
|
||||
SubComponent={(row: any) => {
|
||||
return (
|
||||
<DetailsExpanderContainer listId={row.original.id}/>
|
||||
);
|
||||
}}
|
||||
className="-striped -highlight"/>;
|
||||
}
|
||||
|
||||
private renderColumnVisibilityCheckboxes() {
|
||||
return <div className="d-none d-md-block text-right">
|
||||
Visible: {this.state.columnVisibility.map(
|
||||
(c: IColumnVisibility) => this.renderColumnVisibilityCheckbox(c))}
|
||||
(c: IColumnVisibility, i) => this.renderColumnVisibilityCheckbox(c, i))}
|
||||
</div>;
|
||||
};
|
||||
|
||||
private renderColumnVisibilityCheckbox(props: IColumnVisibility) {
|
||||
return <div className="form-check form-check-inline">
|
||||
<input className="form-check-input" type="checkbox"id={`checkbox${props.column.replace(/\s+/g, "")}`
|
||||
} defaultChecked={props.visible} onChange={() => this.checkColumn(props)}/>
|
||||
<label className="form-check-label" htmlFor={`checkbox${props.column.replace(/\s+/g, "")}`}>{props
|
||||
.column}</label>
|
||||
private renderColumnVisibilityCheckbox(props: IColumnVisibility, key: number) {
|
||||
return <div className="form-check form-check-inline" key={key}>
|
||||
<input className="form-check-input"
|
||||
type="checkbox"
|
||||
id={`checkbox${props.column.replace(/\s+/g, "")}`}
|
||||
defaultChecked={props.visible}
|
||||
onChange={() => this.checkColumn(props)}/>
|
||||
<label className="form-check-label"
|
||||
htmlFor={`checkbox${props.column.replace(/\s+/g, "")}`}>
|
||||
{props.column}
|
||||
</label>
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
|
@ -285,39 +93,4 @@ export class Home extends React.Component<RouteComponentProps<{}>, IHomeState> {
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
interface ISoftwareDto {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface ILanguageDto {
|
||||
iso6391: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface IListDto {
|
||||
id: number;
|
||||
name: string;
|
||||
languages: IListLanguageDto[];
|
||||
softwareIds: number[];
|
||||
tags: IListTagDto[];
|
||||
updatedDate: string;
|
||||
}
|
||||
|
||||
interface IListLanguageDto {
|
||||
name: string;
|
||||
iso6391: string;
|
||||
}
|
||||
|
||||
interface IListTagDto {
|
||||
name: string;
|
||||
colorHex: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface IColumnVisibility {
|
||||
column: string;
|
||||
visible: boolean;
|
||||
}
|
||||
66
src/FilterLists.Web/ClientApp/modules/home/HomeContainer.tsx
Normal file
66
src/FilterLists.Web/ClientApp/modules/home/HomeContainer.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import * as React from "react";
|
||||
import "isomorphic-fetch";
|
||||
import { IListDto } from "./IListDto";
|
||||
import { ISoftwareDto } from "./ISoftwareDto";
|
||||
import { ILanguageDto } from "./ILanguageDto";
|
||||
import "../../utils/loader.css";
|
||||
import { Home } from "./Home";
|
||||
|
||||
interface IState {
|
||||
lists: IListDto[];
|
||||
ruleCount: number;
|
||||
software: ISoftwareDto[];
|
||||
languages: ILanguageDto[];
|
||||
}
|
||||
|
||||
export class HomeContainer extends React.Component<{}, IState> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
lists: [],
|
||||
ruleCount: 0,
|
||||
software: [],
|
||||
languages: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
fetch("https://filterlists.com/api/v1/lists")
|
||||
.then(r => r.json() as Promise<IListDto[]>)
|
||||
.then(d => {
|
||||
this.setState({
|
||||
lists: d
|
||||
});
|
||||
});
|
||||
fetch("https://filterlists.com/api/v1/rules")
|
||||
.then(r => r.json() as Promise<number>)
|
||||
.then(d => {
|
||||
this.setState({
|
||||
ruleCount: d
|
||||
});
|
||||
});
|
||||
fetch("https://filterlists.com/api/v1/software")
|
||||
.then(r => r.json() as Promise<ISoftwareDto[]>)
|
||||
.then(d => {
|
||||
this.setState({
|
||||
software: d
|
||||
});
|
||||
});
|
||||
fetch("https://filterlists.com/api/v1/languages")
|
||||
.then(r => r.json() as Promise<ILanguageDto[]>)
|
||||
.then(d => {
|
||||
this.setState({
|
||||
languages: d
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.state.lists.length === 0 ||
|
||||
this.state.ruleCount === 0 ||
|
||||
this.state.software.length === 0 ||
|
||||
this.state.languages.length === 0
|
||||
? <div className="loader">Loading...</div>
|
||||
: <Home {...this.state}/>;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export interface ILanguageDto {
|
||||
iso6391: string;
|
||||
name: string;
|
||||
}
|
||||
19
src/FilterLists.Web/ClientApp/modules/home/IListDto.ts
Normal file
19
src/FilterLists.Web/ClientApp/modules/home/IListDto.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
export interface IListDto {
|
||||
id: number;
|
||||
name: string;
|
||||
languages: IListLanguageDto[];
|
||||
softwareIds: number[];
|
||||
tags: IListTagDto[];
|
||||
updatedDate: string;
|
||||
}
|
||||
|
||||
interface IListLanguageDto {
|
||||
name: string;
|
||||
iso6391: string;
|
||||
}
|
||||
|
||||
interface IListTagDto {
|
||||
name: string;
|
||||
colorHex: string;
|
||||
description: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export interface ISoftwareDto {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
159
src/FilterLists.Web/ClientApp/modules/home/ListsTable.tsx
Normal file
159
src/FilterLists.Web/ClientApp/modules/home/ListsTable.tsx
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import * as React from "react";
|
||||
import { IListDto } from "./IListDto";
|
||||
import { ISoftwareDto } from "./ISoftwareDto";
|
||||
import { ILanguageDto } from "./ILanguageDto";
|
||||
import ReactTable from "react-table"
|
||||
import "react-table/react-table.css"
|
||||
import "./listsTable.css";
|
||||
import * as moment from "moment";
|
||||
import { getContrast } from "../../utils/GetContrast";
|
||||
import { DetailsExpander } from "./components";
|
||||
|
||||
export interface IColumnVisibility {
|
||||
column: string;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
lists: IListDto[];
|
||||
software: ISoftwareDto[];
|
||||
languages: ILanguageDto[];
|
||||
columnVisibility: IColumnVisibility[];
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export const ListsTable = (props: IProps) => {
|
||||
return <ReactTable
|
||||
data={props.lists}
|
||||
key={props.pageSize}
|
||||
defaultPageSize={props.pageSize}
|
||||
showPageSizeOptions={false}
|
||||
columns={[
|
||||
{
|
||||
Header: "Name",
|
||||
accessor: "name",
|
||||
filterable: true,
|
||||
filterMethod: (f: any, r: any) =>
|
||||
r[f.id].toUpperCase().includes(f.value.toUpperCase()),
|
||||
sortMethod: (a: any, b: any) => a.toUpperCase() > b.toUpperCase() ? 1 : -1,
|
||||
Cell: (c: any) => <h2 className="mb-0">{c.value}</h2>,
|
||||
style: { overflow: "visible" }
|
||||
},
|
||||
{
|
||||
Header: "Software Support",
|
||||
accessor: "softwareIds",
|
||||
filterable: true,
|
||||
filterMethod: (f: any, r: any) =>
|
||||
f.value === "any" || r[f.id].join(",").split(",").includes(f.value),
|
||||
Filter: ({ filter, onChange }) =>
|
||||
<select
|
||||
onChange={(event: any) => onChange(event.target.value)}
|
||||
style={{ width: "100%" }}
|
||||
value={filter ? filter.value : "any"}>
|
||||
<option value="any">Any</option>
|
||||
{props.software.map((s: any, i) => <option value={s.id} key={i}>{s.name}</option>)}
|
||||
</select>,
|
||||
sortable: false,
|
||||
Cell: () => null,
|
||||
width: 250,
|
||||
headerClassName: "d-none d-md-block",
|
||||
className: "d-none d-md-block",
|
||||
show: props.columnVisibility.filter(
|
||||
(c: IColumnVisibility) => { return c.column === "Software"; })[0].visible
|
||||
},
|
||||
{
|
||||
Header: "Tags",
|
||||
accessor: "tags",
|
||||
filterable: true,
|
||||
filterMethod: (f: any, r: any) =>
|
||||
r[f.id].map((e: any) => e.name).join().toUpperCase().includes(f.value.toUpperCase()),
|
||||
sortable: false,
|
||||
Cell: (c: any) =>
|
||||
<div className="fl-tag-container">
|
||||
{c.value.map((e: any, i: any) => <span className="badge"
|
||||
style={{
|
||||
backgroundColor: `#${e.colorHex}`,
|
||||
color: getContrast(`${e.colorHex}`)
|
||||
}}
|
||||
title={e.description}
|
||||
key={i}>
|
||||
{e.name}
|
||||
</span>)}
|
||||
</div>,
|
||||
width: 200,
|
||||
headerClassName: "d-none d-md-block",
|
||||
className: "d-none d-md-block",
|
||||
show: props.columnVisibility.filter(
|
||||
(c: IColumnVisibility) => { return c.column === "Tags"; })[0].visible
|
||||
},
|
||||
{
|
||||
Header: "Languages",
|
||||
accessor: "languages",
|
||||
filterable: true,
|
||||
filterMethod: (f: any, r: any) =>
|
||||
f.value === "any" || r[f.id].map((x: any) => x.iso6391).includes(f.value),
|
||||
Filter: ({ filter, onChange }) =>
|
||||
<select
|
||||
onChange={(event: any) => onChange(event.target.value)}
|
||||
style={{ width: "100%" }}
|
||||
value={filter ? filter.value : "any"}>
|
||||
<option value="any">Any</option>
|
||||
{props.languages.map((l: any, i) => <option value={l.iso6391} key={i}>{l.name}</option>)
|
||||
}
|
||||
</select>,
|
||||
sortable: false,
|
||||
Cell: (c: any) => <div className="fl-tag-container">
|
||||
{c.value.map((e: any, i: any) => <span className="badge badge-secondary"
|
||||
title={e.name}
|
||||
key={i}>
|
||||
{e.iso6391}
|
||||
</span>)}
|
||||
</div>,
|
||||
style: { whiteSpace: "inherit" },
|
||||
width: 100,
|
||||
headerClassName: "d-none d-md-block",
|
||||
className: "d-none d-md-block",
|
||||
show: props.columnVisibility.filter(
|
||||
(c: IColumnVisibility) => { return c.column === "Languages"; })[0].visible
|
||||
},
|
||||
{
|
||||
Header: "Updated",
|
||||
accessor: "updatedDate",
|
||||
filterable: true,
|
||||
filterMethod: (f: any, r: any) => r[f.id].includes(f.value),
|
||||
sortMethod: (a: any, b: any) =>
|
||||
moment(a).isValid() ? (moment(b).isValid() ? (moment(a).isBefore(b) ? -1 : 1) : 1) : -1,
|
||||
Cell: (c: any) => <div>
|
||||
{moment(c.value).isValid() ? moment(c.value).format("l") : "N/A"}
|
||||
</div>,
|
||||
style: { whiteSpace: "inherit" },
|
||||
width: 100,
|
||||
headerClassName: "d-none d-md-block",
|
||||
className: "d-none d-md-block",
|
||||
show: props.columnVisibility.filter(
|
||||
(c: IColumnVisibility) => { return c.column === "Updated Date"; })[0].visible
|
||||
},
|
||||
{
|
||||
Header: "Details",
|
||||
accessor: "id",
|
||||
sortable: false,
|
||||
expander: true,
|
||||
Expander: ({ isExpanded, row }) =>
|
||||
<div>
|
||||
{isExpanded
|
||||
? <button className="btn btn-primary btn-block active"
|
||||
title={`Collapse details about ${row.name}.`}>
|
||||
Details
|
||||
</button>
|
||||
: <button className="btn btn-primary btn-block"
|
||||
title={`Learn more about ${row.name}.`}>
|
||||
Details
|
||||
</button>}
|
||||
</div>,
|
||||
style: { textAlign: "center" },
|
||||
width: 90
|
||||
}
|
||||
]}
|
||||
SubComponent={(r: any) => <DetailsExpander listId={r.original.id}/>}
|
||||
className="-striped -highlight"/>;
|
||||
};
|
||||
15
src/FilterLists.Web/ClientApp/modules/home/Tagline.tsx
Normal file
15
src/FilterLists.Web/ClientApp/modules/home/Tagline.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import * as React from "react";
|
||||
|
||||
interface IProps {
|
||||
ruleCount: number,
|
||||
listCount: number;
|
||||
}
|
||||
|
||||
export const Tagline = (props: IProps) => {
|
||||
return <p className="ml-2 mr-2">
|
||||
The independent, comprehensive directory of <strong>{props.ruleCount.toLocaleString()
|
||||
}</strong> unique rules across <strong>{
|
||||
props.listCount.toLocaleString()
|
||||
}</strong> filter and host lists for advertisements, trackers, malware, and annoyances.
|
||||
</p>;
|
||||
};
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { DetailsExpanderContainer } from "./DetailsExpanderContainer";
|
||||
|
||||
export {
|
||||
DetailsExpanderContainer
|
||||
DetailsExpanderContainer as DetailsExpander
|
||||
};
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { DetailsExpanderContainer } from "./detailsExpanderContainer";
|
||||
import { DetailsExpander } from "./detailsExpander";
|
||||
|
||||
export {
|
||||
DetailsExpanderContainer
|
||||
DetailsExpander
|
||||
};
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Home } from "./Home";
|
||||
import { HomeContainer } from "./HomeContainer";
|
||||
|
||||
export {
|
||||
Home
|
||||
HomeContainer as Home
|
||||
};
|
||||
Loading…
Reference in a new issue