Skip to content

Commit

Permalink
Content explorer quick filter feature
Browse files Browse the repository at this point in the history
  • Loading branch information
hisorange committed Jan 12, 2022
1 parent 884a12b commit 69477dd
Showing 1 changed file with 104 additions and 26 deletions.
130 changes: 104 additions & 26 deletions src/modules/content/component/_router.component.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { SearchOutlined } from '@ant-design/icons';
import { Divider, Input, Layout, Result, Tree, TreeDataNode } from 'antd';
import {
Divider,
Empty,
Input,
Layout,
Result,
Tree,
TreeDataNode,
} from 'antd';
import Sider from 'antd/lib/layout/Sider';
import { QueryBuilder } from 'odata-query-builder';
import React, { useEffect, useState } from 'react';
import { generatePath, Route, Routes, useNavigate } from 'react-router';
import {
generatePath,
Route,
Routes,
useLocation,
useNavigate,
} from 'react-router';
import MenuBlock from '../../admin/component/menu-block.component';
import { useHttpClient } from '../../admin/library/use-http-client';
import { ISchema } from '../../schema';
Expand All @@ -15,23 +29,64 @@ type SchemaWithModule = ISchema & {
module?: IContentModule;
};

type SchemaRouteParams = { database?: string; reference?: string };

const applyQuickFilter =
(filterValue: string) => (schema: SchemaWithModule) => {
if (!filterValue) {
return true;
}

filterValue = filterValue.toLowerCase();
const words = filterValue.replace(/\s+/, ' ').split(' ');

for (const word of words) {
// Match in the title
if (schema.title.toLowerCase().match(word)) {
return true;
}

// In the matched module
if (schema.moduleId) {
if (schema.module.name.toLowerCase().match(word)) {
return true;
}
}
}

return false;
};

export default function ContentRouterComponent() {
const navigate = useNavigate();
const location = useLocation();

const [quickFilter, setQuickFilter] = useState<string>(null);
const [selected, setSelected] = useState<string[]>(null);
const [tree, setTree] = useState<TreeDataNode[]>([]);

const [{ data: schemas, loading, error }] = useHttpClient<SchemaWithModule[]>(
'/api/odata/main/schema' +
new QueryBuilder().top(5000).select('*,module').toQuery(),
new QueryBuilder().top(1_000).select('*,module').toQuery(),
{
useCache: false,
},
);

useEffect(() => {
const segments = location.pathname.split('/').slice(3, 5);

if (segments && segments.length === 2) {
setSelected([`${segments[0]}-${segments[1]}`]);
}
}, [location.pathname]);

useEffect(() => {
if (schemas) {
const modules: IContentModule[] = [];
const tree: TreeDataNode[] = [];

// Collect modules from existing references
for (const schema of schemas
.filter(s => s.module)
.sort((a, b) => (a.title > b.title ? 1 : -1))) {
Expand All @@ -40,35 +95,51 @@ export default function ContentRouterComponent() {
}
}

// Build the module branches
for (const module of modules) {
const children = schemas
.filter(s => s.module)
.filter(s => s.module.id == module.id)
.filter(applyQuickFilter(quickFilter))
.sort((a, b) => (a.title > b.title ? 1 : -1))
.map(s => ({
key: `${s.database}-${s.reference}`,
title: s.title,
isLeaf: true,
}));

// Filtered
if (!children.length) {
continue;
}

tree.push({
title: module.name,
key: `m-${module.id}`,
key: module.id,
selectable: false,
children: schemas
.filter(s => s.module)
.filter(s => s.module.id == module.id)
.sort((a, b) => (a.title > b.title ? 1 : -1))
.map(s => ({
key: `s-${s.database}-${s.reference}`,
title: s.title,
isLeaf: true,
})),
children,
isLeaf: false,
});
}

for (const schema of schemas.filter(s => !s.module)) {
// Add the schemas which are not in any module
for (const schema of schemas
.filter(s => !s.module)
.filter(applyQuickFilter(quickFilter))) {
tree.push({
title: schema.title,
key: `s-${schema.database}-${schema.reference}`,
key: `${schema.database}-${schema.reference}`,
isLeaf: true,
});
}

setTree(tree);
}
}, [loading]);
}, [loading, quickFilter]);

// Clear the quick filter when the location changes
// I assume the user clicked to the match
useEffect(() => setQuickFilter(null), [location.pathname]);

if (error) {
return (
Expand All @@ -89,9 +160,14 @@ export default function ContentRouterComponent() {
>
<div className="px-2 py-2">
<Input
placeholder="Content Search..."
placeholder="Quick Filter"
prefix={<SearchOutlined />}
value={quickFilter}
onChange={e => {
setQuickFilter(e.target?.value ?? null);
}}
size="small"
allowClear
/>
</div>
<Divider className="my-0" />
Expand All @@ -100,23 +176,25 @@ export default function ContentRouterComponent() {
<Tree.DirectoryTree
treeData={tree}
defaultExpandAll
defaultSelectedKeys={selected}
onSelect={selected => {
if (selected.length) {
const [prefix, db, ref] = selected[0].toString().split('-');
const [db, ref] = selected[0].toString().split('-');

if (prefix == 's') {
const path = generatePath('/admin/content/:db/:ref/list', {
db,
ref,
});
const path = generatePath('/admin/content/:db/:ref/list', {
db,
ref,
});

navigate(path);
}
navigate(path);
}
}}
/>
) : undefined}
) : (
<Empty className="m-2" description="No Match" />
)}
</MenuBlock>
<MenuBlock title="Content Transactions"></MenuBlock>
</Sider>

<Layout>
Expand Down

0 comments on commit 69477dd

Please sign in to comment.