api details refactor ref #505

This commit is contained in:
Collin M. Barrett 2018-09-27 16:52:30 -05:00
parent 0c60eff22b
commit 4a2e49cbf3
49 changed files with 269 additions and 552 deletions

View file

@ -23,7 +23,7 @@ public async Task<IActionResult> GetAll() =>
[HttpGet]
[Route("{id}")]
public async Task<IActionResult> GetById(int id) =>
await Get(() => filterListService.GetDetailsAsync(id), id);
await Get(() => filterListService.GetByIdAsync(id), id);
[HttpGet("seed")]
public async Task<IActionResult> Seed() =>

View file

@ -20,10 +20,9 @@ await DbContext.FilterLists
.ProjectTo<FilterListDto>(MapConfig)
.ToListAsync();
public async Task<ListDetails> GetDetailsAsync(int id) =>
public async Task<FilterListDto> GetByIdAsync(int id) =>
await DbContext.FilterLists
.ProjectTo<ListDetails>(MapConfig)
.FirstOrDefaultAsync(l => l.Id == id)
.FilterParentListFromMaintainerAdditionalLists();
.ProjectTo<FilterListDto>(MapConfig)
.FirstOrDefaultAsync(l => l.Id == id);
}
}

View file

@ -1,20 +0,0 @@
using System.Linq;
using System.Threading.Tasks;
using FilterLists.Services.FilterList.Models;
namespace FilterLists.Services.FilterList
{
public static class FilterListServiceExtensions
{
public static async Task<ListDetails> FilterParentListFromMaintainerAdditionalLists(
this Task<ListDetails> listDetailsDtos)
{
foreach (var maintainer in listDetailsDtos.Result.Maintainers)
maintainer.AdditionalLists = maintainer
.AdditionalLists.Where(additionalList =>
additionalList.Id != listDetailsDtos.Result.Id)
.ToList();
return await listDetailsDtos;
}
}
}

View file

@ -1,55 +0,0 @@
using System.Linq;
using AutoMapper;
using FilterLists.Services.FilterList.Models;
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.MappingProfiles
{
[UsedImplicitly]
public class ListDetailsDtoMappingProfile : Profile
{
public ListDetailsDtoMappingProfile() =>
CreateMap<Data.Entities.FilterList, ListDetails>()
.ForMember(dest => dest.Id,
opt => opt.MapFrom(src =>
(int)src.Id))
.ForMember(dest => dest.Languages,
opt => opt.MapFrom(src =>
src.FilterListLanguages.Select(la => la.Language.Name)))
.ForMember(dest => dest.Maintainers,
opt => opt.MapFrom(src =>
src.FilterListMaintainers.Select(m => m.Maintainer)))
.ForMember(dest => dest.Tags,
opt => opt.MapFrom(src =>
src.FilterListTags.Select(m => m.Tag)))
.ForMember(dest => dest.RuleCount,
opt => opt.MapFrom(src =>
src.Snapshots
.Count(s => s.WasSuccessful) > 0
? src.Snapshots
.Where(s => s.WasSuccessful)
.OrderByDescending(s => s.CreatedDateUtc)
.FirstOrDefault()
.SnapshotRules
.Count
: 0))
.ForMember(dest => dest.UpdatedDate,
opt => opt.MapFrom(src =>
src.DiscontinuedDate ??
(src.Snapshots
.Count(s => s.WasSuccessful && s.Md5Checksum != null) >= 2
? src.Snapshots
.Where(s => s.WasSuccessful && s.Md5Checksum != null)
.Select(s => s.CreatedDateUtc)
.OrderByDescending(c => c)
.FirstOrDefault()
: null)))
.ForMember(dest => dest.ViewUrl,
opt => opt.MapFrom(src =>
src.Snapshots
.Where(s => s.WasSuccessful)
.OrderByDescending(s => s.CreatedDateUtc)
.FirstOrDefault()
.WaybackUrl ?? src.ViewUrl));
}
}

View file

@ -1,20 +0,0 @@
using System.Linq;
using AutoMapper;
using FilterLists.Services.FilterList.Models;
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.MappingProfiles
{
[UsedImplicitly]
public class ListMaintainerDtoMappingProfile : Profile
{
public ListMaintainerDtoMappingProfile() =>
CreateMap<Data.Entities.Maintainer, ListMaintainerDto>()
.ForMember(dest => dest.Id,
opt => opt.MapFrom(src =>
(int)src.Id))
.ForMember(dest => dest.AdditionalLists,
opt => opt.MapFrom(src =>
src.FilterListMaintainers.Select(lm => lm.FilterList)));
}
}

View file

@ -1,19 +0,0 @@
using System.Linq;
using AutoMapper;
using FilterLists.Services.FilterList.Models;
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.MappingProfiles
{
[UsedImplicitly]
public class ListSyntaxDtoMappingProfile : Profile
{
public ListSyntaxDtoMappingProfile() =>
CreateMap<Data.Entities.Syntax, ListSyntaxDto>()
.ForMember(dest => dest.SupportedSoftware,
opt => opt.MapFrom(src =>
src.SoftwareSyntaxes
.Select(ss => ss.Software)
.OrderBy(s => s.Name)));
}
}

View file

@ -1,16 +0,0 @@
using AutoMapper;
using FilterLists.Services.FilterList.Models;
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.MappingProfiles
{
[UsedImplicitly]
public class ListTagDtoMappingProfile : Profile
{
public ListTagDtoMappingProfile() =>
CreateMap<Data.Entities.Tag, ListTagDto>()
.ForMember(dest => dest.ColorHex,
opt => opt.MapFrom(src =>
TagColors.Colors[(int)src.Id]));
}
}

View file

@ -1,17 +0,0 @@
using AutoMapper;
using FilterLists.Data.Entities;
using FilterLists.Services.FilterList.Models;
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.MappingProfiles
{
[UsedImplicitly]
public class MaintainerAdditionalListsDtoMappingProfile : Profile
{
public MaintainerAdditionalListsDtoMappingProfile() =>
CreateMap<Data.Entities.FilterList, MaintainerAdditionalListsDto>()
.ForMember(dest => dest.Id,
opt => opt.MapFrom(src =>
(int)src.Id));
}
}

View file

@ -1,16 +0,0 @@
using AutoMapper;
using FilterLists.Services.FilterList.Models;
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.MappingProfiles
{
[UsedImplicitly]
public class SyntaxSupportedSoftwareDtoMappingProfile : Profile
{
public SyntaxSupportedSoftwareDtoMappingProfile() =>
CreateMap<Data.Entities.Software, SyntaxSupportedSoftwareDto>()
.ForMember(dest => dest.Id,
opt => opt.MapFrom(src =>
(int)src.Id));
}
}

View file

@ -1,35 +0,0 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.Models
{
[UsedImplicitly]
public class ListDetails
{
public int Id { get; set; }
public string ChatUrl { get; set; }
public string Description { get; set; }
public string DescriptionSourceUrl { get; set; }
public DateTime? DiscontinuedDate { get; set; }
public string DonateUrl { get; set; }
public string EmailAddress { get; set; }
public string ForumUrl { get; set; }
public string HomeUrl { get; set; }
public string IssuesUrl { get; set; }
public IEnumerable<string> Languages { get; set; }
public ListLicenseDto License { get; set; }
public IEnumerable<ListMaintainerDto> Maintainers { get; set; }
public string Name { get; set; }
public string PolicyUrl { get; set; }
public DateTime? PublishedDate { get; set; }
public int RuleCount { get; set; }
public string SubmissionUrl { get; set; }
public ListSyntaxDto Syntax { get; set; }
public IEnumerable<ListTagDto> Tags { get; set; }
public DateTime? UpdatedDate { get; set; }
public string ViewUrl { get; set; }
public string ViewUrlMirror1 { get; set; }
public string ViewUrlMirror2 { get; set; }
}
}

View file

@ -1,11 +0,0 @@
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.Models
{
[UsedImplicitly]
public class ListLanguagesDto
{
public string Name { get; set; }
public string Iso6391 { get; set; }
}
}

View file

@ -1,11 +0,0 @@
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.Models
{
[UsedImplicitly]
public class ListLicenseDto
{
public string DescriptionUrl { get; set; }
public string Name { get; set; }
}
}

View file

@ -1,16 +0,0 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.Models
{
[UsedImplicitly]
public class ListMaintainerDto
{
public int Id { get; set; }
public string EmailAddress { get; set; }
public string HomeUrl { get; set; }
public string Name { get; set; }
public string TwitterHandle { get; set; }
public IEnumerable<MaintainerAdditionalListsDto> AdditionalLists { get; set; }
}
}

View file

@ -1,13 +0,0 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.Models
{
[UsedImplicitly]
public class ListSyntaxDto
{
public string DefinitionUrl { get; set; }
public string Name { get; set; }
public IEnumerable<SyntaxSupportedSoftwareDto> SupportedSoftware { get; set; }
}
}

View file

@ -1,12 +0,0 @@
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.Models
{
[UsedImplicitly]
public class ListTagDto
{
public string Name { get; set; }
public string ColorHex { get; set; }
public string Description { get; set; }
}
}

View file

@ -1,11 +0,0 @@
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.Models
{
[UsedImplicitly]
public class MaintainerAdditionalListsDto
{
public int Id { get; set; }
public string Name { get; set; }
}
}

View file

@ -1,12 +0,0 @@
using JetBrains.Annotations;
namespace FilterLists.Services.FilterList.Models
{
[UsedImplicitly]
public class SyntaxSupportedSoftwareDto
{
public int Id { get; set; }
public string HomeUrl { get; set; }
public string Name { get; set; }
}
}

View file

@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace FilterLists.Services.FilterList
{
public static class TagColors
{
//https://stackoverflow.com/a/4382138/2343739
public static readonly IList<string> Colors = new ReadOnlyCollection<string>(new List<string>
{
"FFB300", // Vivid Yellow
"803E75", // Strong Purple
"FF6800", // Vivid Orange
"A6BDD7", // Very Light Blue
"C10020", // Vivid Red
"CEA262", // Grayish Yellow
"817066", // Medium Gray
"007D34", // Vivid Green
"F6768E", // Strong Purplish Pink
"00538A", // Strong Blue
"FF7A5C", // Strong Yellowish Pink
"53377A", // Strong Violet
"FF8E00", // Vivid Orange Yellow
"B32851", // Strong Purplish Red
"F4C800", // Vivid Greenish Yellow
"7F180D", // Strong Reddish Brown
"93AA00", // Vivid Yellowish Green
"593315", // Deep Yellowish Brown
"F13A13", // Vivid Reddish Orange
"232C16" // Dark Olive Green
});
}
}

View file

@ -1,6 +1,6 @@
import * as React from "react";
import { IColumnVisibility, ILanguage, IList, ISoftware, ITag } from "./interfaces";
import { Tagline, ListsTable } from "./components";
import { IColumnVisibility, ILanguage, IList, IMaintainer, ISoftware, ITag } from "./interfaces";
import { ListsTable, Oneliner } from "./components";
const columnVisibilityDefaults: IColumnVisibility[] = [
{ column: "Software", visible: true },
@ -12,6 +12,7 @@ const columnVisibilityDefaults: IColumnVisibility[] = [
interface IProps {
languages: ILanguage[];
lists: IList[];
maintainers: IMaintainer[];
ruleCount: number;
software: ISoftware[];
tags: ITag[];
@ -44,7 +45,7 @@ export class Home extends React.Component<IProps, IState> {
render() {
return <div>
<Tagline listCount={this.props.lists.length} ruleCount={this.props.ruleCount}/>
<Oneliner listCount={this.props.lists.length} ruleCount={this.props.ruleCount}/>
<ListsTable {...this.props} {...this.state}/>
{this.renderColumnVisibilityCheckboxes()}
</div>;

View file

@ -1,11 +1,12 @@
import * as React from "react";
import "isomorphic-fetch";
import { ILanguage, IList, ISoftware, ITag } from "./interfaces";
import { ILanguage, IList, IMaintainer, ISoftware, ITag } from "./interfaces";
import { Home } from "./Home";
interface IState {
languages: ILanguage[];
lists: IList[];
maintainers: IMaintainer[];
ruleCount: number;
software: ISoftware[];
tags: ITag[];
@ -17,6 +18,7 @@ export class HomeContainer extends React.Component<{}, IState> {
this.state = {
languages: [],
lists: [],
maintainers: [],
ruleCount: 0,
software: [],
tags: []
@ -25,40 +27,47 @@ export class HomeContainer extends React.Component<{}, IState> {
componentDidMount() {
this.fetchLists();
this.fetchLanguages();
this.fetchMaintainers();
this.fetchSoftware();
this.fetchTags();
this.fetchLanguages();
this.fetchRuleCount();
};
fetchLists() {
fetch("https://filterlists.com/api/v1/lists")
.then(r => r.json() as Promise<IList[]>)
.then(d => { this.setState({ lists: d }); });
};
fetchSoftware() {
fetch("https://filterlists.com/api/v1/software")
.then(r => r.json() as Promise<ISoftware[]>)
.then(d => { this.setState({ software: d }); });
};
fetchTags() {
fetch("https://filterlists.com/api/v1/tags")
.then(r => r.json() as Promise<ITag[]>)
.then(d => { this.setState({ tags: d }); });
.then(p => { this.setState({ lists: p }); });
};
fetchLanguages() {
fetch("https://filterlists.com/api/v1/languages")
.then(r => r.json() as Promise<ILanguage[]>)
.then(d => { this.setState({ languages: d }); });
.then(p => { this.setState({ languages: p }); });
};
fetchMaintainers() {
fetch("https://filterlists.com/api/v1/maintainers")
.then(r => r.json() as Promise<IMaintainer[]>)
.then(p => { this.setState({ maintainers: p }); });
};
fetchSoftware() {
fetch("https://filterlists.com/api/v1/software")
.then(r => r.json() as Promise<ISoftware[]>)
.then(p => { this.setState({ software: p }); });
};
fetchTags() {
fetch("https://filterlists.com/api/v1/tags")
.then(r => r.json() as Promise<ITag[]>)
.then(p => { this.setState({ tags: p }); });
};
fetchRuleCount() {
fetch("https://filterlists.com/api/v1/rules")
.then(r => r.json() as Promise<number>)
.then(d => { this.setState({ ruleCount: d }); });
.then(p => { this.setState({ ruleCount: p }); });
};
render() {

View file

@ -5,7 +5,7 @@ interface IProps {
ruleCount: number;
};
export const Tagline = (props: IProps) =>
export const Oneliner = (props: IProps) =>
props.listCount > 0 && props.ruleCount > 0
? <p className="ml-2 mr-2">
The independent, comprehensive directory of <strong>{props.ruleCount.toLocaleString()

View file

@ -2,16 +2,35 @@ import * as React from "react";
import { ITag } from "../interfaces";
import { getContrast } from "../../../utils";
export const Tag = (props: ITag) => {
const hexColor = kelly_colors_hex[props.id % kelly_colors_hex.length];
return <span className="badge"
style={{
backgroundColor: `#${hexColor}`,
color: getContrast(hexColor)
}}
title={props.description}>
{props.name}
</span>;
interface IProps {
tags: ITag[];
};
export const TagGroup = (props: IProps) =>
props.tags && props.tags.length > 0
? <div className="fl-tag-container">
{props.tags.map((t: ITag, i: number) => <Tag tag={t} key={i}/>)}
</div>
: null;
interface ITagProps {
tag: ITag;
};
const Tag = (props: ITagProps) => {
if (props.tag) {
const hexColor = kelly_colors_hex[props.tag.id % kelly_colors_hex.length];
return <span className="badge"
style={{
backgroundColor: `#${hexColor}`,
color: getContrast(hexColor)
}}
title={props.tag.description}>
{props.tag.name}
</span>;
} else {
return null;
}
};
//https://stackoverflow.com/a/4382138/2343739

View file

@ -1,10 +1,10 @@
import * as React from "react";
import { IFilterListDetailsDto } from "./IFilterListDetailsDto";
import { IListDetails } from "./IListDetails";
import { InfoCard } from "./infoCard";
import { LinkButtonGroup } from "./LinkButtonGroup";
import { MaintainersInfoCard } from "./maintainersInfoCard";
export const DetailsExpander = (props: IFilterListDetailsDto) => {
export const DetailsExpander = (props: IListDetails) => {
return <div className="card border-primary">
<div className="card-body p-2">
<div className="container m-0">

View file

@ -1,42 +1,25 @@
import * as React from "react";
import "isomorphic-fetch";
import "../../../../utils/loader.css";
import { IFilterListDetailsDto } from "./IFilterListDetailsDto";
import { IListDetails } from "./IListDetails";
import { DetailsExpander } from "./DetailsExpander";
interface IProps {
listId: number;
list: IListDetails;
}
interface IState {
filterListDetails: IFilterListDetailsDto;
}
export class DetailsExpanderContainer extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
filterListDetails: {} as IFilterListDetailsDto
};
}
componentDidMount() {
this.fetchFilterListDetails();
}
fetchFilterListDetails() {
fetch(`https://filterlists.com/api/v1/lists/${this.props.listId}`)
.then(r => r.json() as Promise<IFilterListDetailsDto>)
.then(d => {
this.setState({
filterListDetails: d
});
});
this.state = {};
}
render() {
return this.state.filterListDetails.name
? <DetailsExpander {...this.state.filterListDetails}/>
return this.props.list.name
? <DetailsExpander {...this.props.list}/>
: <div className="loader">Loading...</div>;;
}
}

View file

@ -1,62 +0,0 @@
export interface IFilterListDetailsDto {
chatUrl: string;
description: string;
descriptionSourceUrl: string;
discontinuedDate: string;
donateUrl: string;
emailAddress: string;
forumUrl: string;
homeUrl: string;
issuesUrl: string;
languages: string[];
license: IListLicenseDto;
maintainers: IListMaintainerDto[];
name: string;
policyUrl: string;
publishedDate: string;
ruleCount: number;
submissionUrl: string;
syntax: IListSyntaxDto;
tags: IListTagDto[];
updatedDate: string;
viewUrl: string;
viewUrlMirror1: string;
viewUrlMirror2: string;
}
export interface IListLicenseDto {
descriptionUrl: string;
name: string;
}
export interface IListMaintainerDto {
id: number;
emailAddress: string;
homeUrl: string;
name: string;
twitterHandle: string;
additionalLists: IMaintainerAdditionalListDto[];
}
export interface IListSyntaxDto {
definitionUrl: string;
name: string;
supportedSoftware: ISyntaxSupportedSoftwareDto[];
}
export interface IListTagDto {
name: string;
colorHex: string;
description: string;
}
export interface IMaintainerAdditionalListDto {
id: number;
name: string;
}
export interface ISyntaxSupportedSoftwareDto {
homeUrl: string;
id: number;
name: string;
}

View file

@ -0,0 +1,44 @@
import { ILanguage, IMaintainer, ITag } from "../../interfaces";
export interface IListDetails {
id: number;
chatUrl: string;
description: string;
descriptionSourceUrl: string;
discontinuedDate: string;
donateUrl: string;
emailAddress: string;
forumUrl: string;
homeUrl: string;
issuesUrl: string;
languages: ILanguage[];
license: IListLicense;
maintainers: IMaintainer[];
name: string;
policyUrl: string;
publishedDate: string;
ruleCount: number;
submissionUrl: string;
syntax: IListSyntax;
tags: ITag[];
updatedDate: string;
viewUrl: string;
viewUrlMirrors: string[];
}
export interface IListLicense {
descriptionUrl: string;
name: string;
}
export interface IListSyntax {
definitionUrl: string;
name: string;
supportedSoftware: ISyntaxSupportedSoftware[];
}
export interface ISyntaxSupportedSoftware {
homeUrl: string;
id: number;
name: string;
}

View file

@ -23,15 +23,13 @@ interface IProps {
policyUrl: string;
submissionUrl: string;
viewUrl: string;
viewUrlMirror1: string;
viewUrlMirror2: string;
viewUrlMirrors: string[];
}
export const LinkButtonGroup = (props: IProps) => {
const viewUrlMirrors = new Array(props.viewUrlMirror1, props.viewUrlMirror2).filter(u => u);
return <div className="col-3 p-0 btn-group-vertical justify-content-start d-flex align-items-end">
<SubscribeButtonGroup name={props.name} url={props.viewUrl} urlMirrors={viewUrlMirrors}/>
<ViewButtonGroup name={props.name} url={props.viewUrl} urlMirrors={viewUrlMirrors}/>
<SubscribeButtonGroup name={props.name} url={props.viewUrl} urlMirrors={props.viewUrlMirrors}/>
<ViewButtonGroup name={props.name} url={props.viewUrl} urlMirrors={props.viewUrlMirrors}/>
<HomeButton name={props.name} url={props.homeUrl}/>
<PolicyButton name={props.name} url={props.policyUrl}/>
<DonateButton name={props.name} url={props.donateUrl}/>

View file

@ -1,5 +1,7 @@
import { DetailsExpanderContainer } from "./DetailsExpanderContainer";
import { IListDetails } from "./IListDetails";
export {
DetailsExpanderContainer as DetailsExpander
DetailsExpanderContainer as DetailsExpander,
IListDetails
};

View file

@ -1,5 +1,6 @@
import * as React from "react";
import { IListLicenseDto, IListSyntaxDto, IListTagDto, ISyntaxSupportedSoftwareDto } from "../IFilterListDetailsDto";
import { IListLicense, IListSyntax, ISyntaxSupportedSoftware } from "../IListDetails";
import { ILanguage, ITag } from "../../../interfaces";
import { Description } from "./Description";
import { DiscontinuedDate } from "./DiscontinuedDate";
import { Languages } from "./Languages";
@ -8,30 +9,30 @@ import { PublishedDate } from "./PublishedDate";
import { RuleCount } from "./RuleCount";
import { SoftwareIcon } from "../../softwareIcon";
import { Syntax } from "./Syntax";
import { Tags } from "./Tags";
import { TagGroup } from "../../TagGroup"
import { UpdatedDate } from "./UpdatedDate";
interface IProps {
description: string;
descriptionSourceUrl: string;
discontinuedDate: string;
languages: string[];
license: IListLicenseDto;
languages: ILanguage[];
license: IListLicense;
name: string;
publishedDate: string;
ruleCount: number;
syntax: IListSyntaxDto;
tags: IListTagDto[];
syntax: IListSyntax;
tags: ITag[];
updatedDate: string;
}
export const InfoCard = (props: IProps) => {
return <div className="col-9">
<Tags tags={props.tags}/>
<TagGroup tags={props.tags}/>
<div className="d-md-none">
{props.syntax
? props.syntax.supportedSoftware.map(
(s: ISyntaxSupportedSoftwareDto, i: number) =>
(s: ISyntaxSupportedSoftware, i: number) =>
<a href={s.homeUrl} key={i}>
<SoftwareIcon id={s.id} key={i}/>
</a>)
@ -39,7 +40,7 @@ export const InfoCard = (props: IProps) => {
</div>
<Description {...props} url={props.descriptionSourceUrl}/>
<ul className="list-group list-group-flush">
<Languages languages={props.languages}/>
<Languages languages={...props.languages}/>
<RuleCount {...props}/>
<DiscontinuedDate date={props.discontinuedDate}/>
<UpdatedDate {...props}/>

View file

@ -1,20 +1,21 @@
import * as React from "react";
import { ILanguage } from "../../../interfaces";
interface IProps {
languages: string[];
languages: ILanguage[];
}
export const Languages = (props: IProps) => {
return props.languages.length > 0
return props.languages && props.languages.length > 0
? props.languages.length > 1
? <li className="list-group-item">
<p className="m-0">Languages:</p>
<ul>
{props.languages.map((language: string, i: number) => <li key={i}>{language}</li>)}
{props.languages.map((language: ILanguage, i: number) => <li key={i}>{language.name}</li>)}
</ul>
</li>
: <li className="list-group-item">
<p>Language: {props.languages[0]}</p>
<p>Language: {props.languages[0].name}</p>
</li>
: null;
};

View file

@ -1,5 +1,5 @@
import * as React from "react";
import { IListLicenseDto } from "../IFilterListDetailsDto";
import { IListLicense as IListLicenseDto } from "../IListDetails";
export const License = (props: IListLicenseDto) => {
return props.name

View file

@ -1,5 +1,5 @@
import * as React from "react";
import { IListSyntaxDto } from "../IFilterListDetailsDto";
import { IListSyntax as IListSyntaxDto } from "../IListDetails";
export const Syntax = (props: IListSyntaxDto) => {
return props.name

View file

@ -1,19 +0,0 @@
import * as React from "react";
import { IListTagDto } from "../IFilterListDetailsDto";
import { getContrast } from "../../../../../utils";
export const Tag = (props: IListTagDto) => {
const style = props.colorHex
? {
backgroundColor: `#${props.colorHex}`,
color: getContrast(`${props.colorHex}`)
}
: undefined;
return props.name
? <span className="badge"
style={style}
title={props.description}>
{props.name}
</span>
: null;
};

View file

@ -1,15 +0,0 @@
import * as React from "react";
import { IListTagDto } from "../IFilterListDetailsDto";
import { Tag } from "./Tag";
interface IProps {
tags: IListTagDto[];
}
export const Tags = (props: IProps) => {
return props.tags && props.tags.length > 0
? <div className="d-md-none">
{props.tags.map((t: IListTagDto, i: number) => <Tag {...t} key={i}/>)}
</div>
: null;
};

View file

@ -1,14 +1,13 @@
import * as React from "react";
import { IMaintainerAdditionalListDto } from "../IFilterListDetailsDto";
interface IProps {
name: string;
additionalLists: IMaintainerAdditionalListDto[];
}
//interface IProps {
// name: string;
// additionalLists: IMaintainerAdditionalListDto[];
//}
export const MaintainerAdditionalLists = (props: IProps) => {
export const MaintainerAdditionalLists = (/*props: IProps*/) => {
return <div className="col-9">
{props.additionalLists && props.additionalLists.length > 0
{ /*props.additionalLists && props.additionalLists.length > 0
? <div>
<h4>More by {props.name}:</h4>
<ul>
@ -17,6 +16,6 @@ export const MaintainerAdditionalLists = (props: IProps) => {
</ul>
</div>
: null}
: null*/ }
</div>;
};

View file

@ -1,20 +1,23 @@
import * as React from "react";
import { IListMaintainerDto } from "../IFilterListDetailsDto";
import { IMaintainer } from "../../../interfaces";
import { MaintainerAdditionalLists } from "./MaintainerAdditionalLists";
import { MaintainerLinkButtonGroup } from "./MaintainerLinkButtonGroup";
export const MaintainerInfoCard = (props: IListMaintainerDto) => {
return props.name
? <div className="card">
<div className="card-body">
<h3 className="card-header">Maintained by {props.name}</h3>
<div className="container pt-1">
<div className="row">
<MaintainerAdditionalLists {...props}/>
<MaintainerLinkButtonGroup {...props}/>
</div>
</div>
</div>
</div>
: null;
};
interface IProps {
maintainer: IMaintainer;
}
export const MaintainerInfoCard = (props: IProps) =>
props.maintainer.name
? <div className="card">
<div className="card-body">
<h3 className="card-header">Maintained by {props.maintainer.name}</h3>
<div className="container pt-1">
<div className="row">
<MaintainerAdditionalLists/>
<MaintainerLinkButtonGroup {...props.maintainer}/>
</div>
</div>
</div>
</div>
: null;

View file

@ -8,10 +8,9 @@ interface IProps {
twitterHandle: string;
}
export const MaintainerLinkButtonGroup = (props: IProps) => {
return <div className="col-3 p-0 btn-group-vertical justify-content-start d-flex align-items-end" role="group">
<HomeButton {...props} url={props.homeUrl}/>
<EmailButton {...props}/>
<TwitterButton {...props}/>
</div>;
};
export const MaintainerLinkButtonGroup = (props: IProps) =>
<div className="col-3 p-0 btn-group-vertical justify-content-start d-flex align-items-end" role="group">
<HomeButton {...props} url={props.homeUrl}/>
<EmailButton {...props}/>
<TwitterButton {...props}/>
</div>;

View file

@ -1,16 +1,14 @@
import * as React from "react";
import { IListMaintainerDto } from "../IFilterListDetailsDto";
import { IMaintainer } from "../../../interfaces";
import { MaintainerInfoCard } from "./MaintainerInfoCard";
interface IProps {
maintainers: IListMaintainerDto[];
maintainers: IMaintainer[];
}
export const MaintainersInfoCard = (props: IProps) => {
return props.maintainers && props.maintainers.length > 0
? <div className="w-100">
{props.maintainers.map(
(m: IListMaintainerDto, i: number) => <MaintainerInfoCard {...m} key={i}/>)}
</div>
: null;
};
export const MaintainersInfoCard = (props: IProps) =>
props.maintainers && props.maintainers.length > 0
? <div className="w-100">
{props.maintainers.map((m: IMaintainer, i: number) => <MaintainerInfoCard maintainer={m} key={i}/>)}
</div>
: null;

View file

@ -1,9 +1,9 @@
import { DetailsExpander } from "./detailsExpander";
import { ListsTable } from "./listsTable";
import { Tagline } from "./Tagline";
import { Oneliner } from "./Oneliner";
export {
DetailsExpander,
ListsTable,
Tagline
Oneliner
};

View file

@ -1,15 +1,17 @@
import * as React from "react";
import { IColumnVisibility, ILanguage, IList, ISoftware, ITag } from "../../interfaces";
import { IColumnVisibility, ILanguage, IList, IMaintainer, ISoftware, ITag } from "../../interfaces";
import "../../../../utils/loader.css";
import ReactTable from "react-table"
import "react-table/react-table.css"
import "./listsTable.css";
import { DetailsButton, Languages, Name, Software, Tags, UpdatedDate } from "./columns";
import { IListDetails } from "../../components/detailsExpander";
import { DetailsExpander } from "../../components";
interface IProps {
languages: ILanguage[];
lists: IList[];
maintainers: IMaintainer[];
software: ISoftware[];
tags: ITag[];
columnVisibility: IColumnVisibility[];
@ -17,7 +19,7 @@ interface IProps {
};
export const ListsTable = (props: IProps) =>
props.lists.length > 0 && props.languages.length > 0 && props.software.length > 0
props.languages.length > 0 && props.lists.length > 0 && props.software.length > 0 && props.tags.length > 0
? <ReactTable
data={props.lists}
defaultPageSize={props.pageSize}
@ -31,6 +33,50 @@ export const ListsTable = (props: IProps) =>
DetailsButton
]}
defaultSorted={[{ id: "name" }]}
SubComponent={(r: any) => <DetailsExpander listId={r.original.id}/>}
SubComponent={(r: any) => <DetailsExpander list={mapListDetails(({
list: r.original,
languages: props.languages,
maintainers: props.maintainers,
tags: props.tags
} as ICreateListDtoProps))}/>}
className="-striped -highlight"/>
: <div className="loader">Loading...</div>;
: <div className="loader">Loading...</div>;
interface ICreateListDtoProps {
list: IList;
languages: ILanguage[];
maintainers: IMaintainer[];
tags: ITag[];
};
const mapListDetails = (props: ICreateListDtoProps): IListDetails => {
return {
id: props.list.id,
chatUrl: props.list.chatUrl,
description: props.list.description,
descriptionSourceUrl: props.list.descriptionSourceUrl,
discontinuedDate: props.list.discontinuedDate,
donateUrl: props.list.donateUrl,
emailAddress: props.list.emailAddress,
forumUrl: props.list.forumUrl,
homeUrl: props.list.homeUrl,
issuesUrl: props.list.issuesUrl,
languages: props.list.languageIds
? props.languages.filter((l: ILanguage) => props.list.languageIds.indexOf(l.id) > -1)
: undefined,
//license:,
maintainers: props.list.maintainerIds
? props.maintainers.filter((m: IMaintainer) => props.list.maintainerIds.indexOf(m.id) > -1)
: undefined,
name: props.list.name,
policyUrl: props.list.policyUrl,
publishedDate: props.list.publishedDate,
ruleCount: props.list.ruleCount,
submissionUrl: props.list.submissionUrl,
//syntax:,
tags: props.list.tagIds ? props.tags.filter((t: ITag) => props.list.tagIds.indexOf(t.id) > -1) : undefined,
updatedDate: props.list.updatedDate,
viewUrl: props.list.viewUrl,
viewUrlMirrors: props.list.viewUrlMirrors
} as IListDetails;
};

View file

@ -1,6 +1,6 @@
import * as React from "react";
import { Column, Filter } from "react-table";
import { Tag } from "../../Tag"
import { TagGroup } from "../../TagGroup"
import { IColumnVisibility, ITag } from "../../../interfaces";
export const Tags = (columnVisibility: IColumnVisibility[], tags: ITag[]) => {
@ -41,9 +41,6 @@ const sortMethod = (a: number[], b: number[]): any =>
const Cell = (tagIds: number[], tags: ITag[]) =>
tagIds
? <div className="fl-tag-container">
{tagIds.map((tid: number, i: number) => {
const tag = tags.filter((t: ITag) => t.id === tid)[0];
return <Tag {...tag} key={i}/>;
})}
<TagGroup tags={tags.filter((t: ITag) => tagIds.indexOf(t.id) > -1)}/>
</div>
: null;

View file

@ -57,4 +57,4 @@ const icons: { [id: number]: IIcon; } = {
19: { image: img19, imageTitle: "Samsung Knox" },
20: { image: img20, imageTitle: "Little Snitch" },
21: { image: img21, imageTitle: "Privoxy" }
};
};

View file

@ -1,5 +1,6 @@
export interface ILanguage {
id: number;
filterListIds: number[];
iso6391: string;
name: string;
}

View file

@ -1,8 +1,25 @@
export interface IList {
id: number;
chatUrl: string;
description: string;
descriptionSourceUrl: string;
discontinuedDate: string;
donateUrl: string;
emailAddress: string;
forumUrl: string;
homeUrl: string;
issuesUrl: string;
languageIds: number[];
licenseId: string;
maintainerIds: number[];
name: string;
policyUrl: string;
publishedDate: string;
ruleCount: number;
submissionUrl: string;
syntaxId: number;
tagIds: number[];
updatedDate: string;
viewUrl: string;
viewUrlMirrors: string[];
}

View file

@ -0,0 +1,8 @@
export interface IMaintainer {
id: number;
emailAddress: string;
filterListIds: number[];
homeUrl: string;
name: string;
twitterHandle: string;
}

View file

@ -1,5 +1,7 @@
export interface ISoftware {
id: number;
homeUrl: string;
isAbpSubscribable: boolean;
name: string;
syntaxIds: number[];
}

View file

@ -1,5 +1,6 @@
export interface ITag {
id: number;
description: string;
filterListIds: number[];
name: string;
}

View file

@ -1,6 +1,7 @@
import { IColumnVisibility } from "./IColumnVisibility";
import { ILanguage } from "./ILanguage";
import { IList } from "./IList";
import { IMaintainer } from "./IMaintainer";
import { ISoftware } from "./ISoftware";
import { ITag } from "./ITag";
@ -9,6 +10,7 @@ export {
IColumnVisibility,
ILanguage,
IList,
IMaintainer,
ISoftware,
ITag
};

View file

@ -3,11 +3,11 @@
.loader,
.loader:before,
.loader:after {
-webkit-animation: load1 1s infinite ease-in-out;
animation: load1 1s infinite ease-in-out;
background: #000000;
height: 4em;
width: 1em;
-webkit-animation: load1 1s infinite ease-in-out;
animation: load1 1s infinite ease-in-out;
background: #000000;
height: 4em;
width: 1em;
}
.loader {