{row.getVisibleCells().map((cell) => {
// see https://tanstack.com/table/v8/docs/api/core/column-def#meta to type this correctly
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
const meta: any = cell.column.columnDef.meta;
return (
diff --git a/webapp/src/dogma/common/components/table/DynamicDataTable.tsx b/webapp/src/dogma/common/components/table/DynamicDataTable.tsx
index 4a3328863f..e7eae9e327 100644
--- a/webapp/src/dogma/common/components/table/DynamicDataTable.tsx
+++ b/webapp/src/dogma/common/components/table/DynamicDataTable.tsx
@@ -13,14 +13,16 @@ import {
import { DataTable } from 'dogma/common/components/table/DataTable';
import { Filter } from 'dogma/common/components/table/Filter';
import { PaginationBar } from 'dogma/common/components/table/PaginationBar';
-import { Dispatch, SetStateAction, useState } from 'react';
+import React, { ReactElement, useState } from 'react';
export type DynamicDataTableProps = {
data: Data[];
columns: ColumnDef[];
pagination?: { pageIndex: number; pageSize: number };
- setPagination?: Dispatch>;
+ setPagination?: (updater: (old: PaginationState) => PaginationState) => void;
pageCount?: number;
+ disableGotoButton?: boolean;
+ onEmptyData?: ReactElement;
};
export const DynamicDataTable = ({
@@ -29,6 +31,8 @@ export const DynamicDataTable = ({
pagination,
setPagination,
pageCount,
+ disableGotoButton,
+ onEmptyData,
}: DynamicDataTableProps) => {
const [sorting, setSorting] = useState([]);
const [columnFilters, setColumnFilters] = useState([]);
@@ -53,13 +57,19 @@ export const DynamicDataTable = ({
return (
<>
- Filter by {table.getHeaderGroups()[0].headers[0].id}
-
-
- {pagination && }
+ {table.getRowModel().rows.length == 0 && onEmptyData ? (
+ onEmptyData
+ ) : (
+ <>
+ Filter by {table.getHeaderGroups()[0].headers[0].id}
+
+
+ >
+ )}
+ {pagination && }
>
);
};
diff --git a/webapp/src/dogma/common/components/table/PaginationBar.tsx b/webapp/src/dogma/common/components/table/PaginationBar.tsx
index bb17b1a4a6..4e6ca9e959 100644
--- a/webapp/src/dogma/common/components/table/PaginationBar.tsx
+++ b/webapp/src/dogma/common/components/table/PaginationBar.tsx
@@ -1,10 +1,16 @@
-import { Flex, Input, Text, Select, Spacer, IconButton } from '@chakra-ui/react';
+import { Flex, IconButton, Input, Select, Spacer, Text } from '@chakra-ui/react';
import { Table as ReactTable } from '@tanstack/react-table';
import { MdNavigateBefore, MdNavigateNext, MdSkipNext, MdSkipPrevious } from 'react-icons/md';
-export const PaginationBar = ({ table }: { table: ReactTable }) => {
+type PaginationBarProps = {
+ table: ReactTable;
+ disableGotoButton?: boolean;
+};
+
+export const PaginationBar = ({ table, disableGotoButton }: PaginationBarProps) => {
return (
+ {disableGotoButton && }
}
@@ -34,29 +40,33 @@ export const PaginationBar = ({ table }: { table: ReactTabl
{table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
- Go to page:
- {
- const page = e.target.value ? Number(e.target.value) - 1 : 0;
- table.setPageIndex(page);
- }}
- width={20}
- />
-
+ {!disableGotoButton && (
+ <>
+ Go to page:
+ {
+ const page = e.target.value ? Number(e.target.value) - 1 : 0;
+ table.setPageIndex(page);
+ }}
+ width={20}
+ />
+
+ >
+ )}
);
};
diff --git a/webapp/src/dogma/features/api/apiSlice.ts b/webapp/src/dogma/features/api/apiSlice.ts
index e5457081f5..6e576c91d7 100644
--- a/webapp/src/dogma/features/api/apiSlice.ts
+++ b/webapp/src/dogma/features/api/apiSlice.ts
@@ -66,7 +66,7 @@ export type GetFileContent = {
projectName: string;
repoName: string;
filePath: string;
- revision: string;
+ revision: string | number;
};
export type TitleDto = {
@@ -328,6 +328,7 @@ export const apiSlice = createApi({
query: ({ projectName, id }) => `/api/v1/projects/${projectName}/mirrors/${id}`,
providesTags: ['Metadata'],
}),
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
addNewMirror: builder.mutation({
query: (mirror) => ({
url: `/api/v1/projects/${mirror.projectName}/mirrors`,
@@ -336,6 +337,7 @@ export const apiSlice = createApi({
}),
invalidatesTags: ['Metadata'],
}),
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
updateMirror: builder.mutation({
query: ({ projectName, id, mirror }) => ({
url: `/api/v1/projects/${projectName}/mirrors/${id}`,
@@ -352,6 +354,7 @@ export const apiSlice = createApi({
query: ({ projectName, id }) => `/api/v1/projects/${projectName}/credentials/${id}`,
providesTags: ['Metadata'],
}),
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
addNewCredential: builder.mutation({
query: ({ projectName, credential }) => ({
url: `/api/v1/projects/${projectName}/credentials`,
@@ -360,6 +363,7 @@ export const apiSlice = createApi({
}),
invalidatesTags: ['Metadata'],
}),
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
updateCredential: builder.mutation({
query: ({ projectName, id, credential }) => ({
url: `/api/v1/projects/${projectName}/credentials/${id}`,
diff --git a/webapp/src/dogma/features/file/FileDto.ts b/webapp/src/dogma/features/file/FileDto.ts
index 37e8e67377..17228ff804 100644
--- a/webapp/src/dogma/features/file/FileDto.ts
+++ b/webapp/src/dogma/features/file/FileDto.ts
@@ -5,5 +5,6 @@ export interface FileDto {
revision: number;
type: FileType;
url: string;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
content?: string | any;
}
diff --git a/webapp/src/dogma/features/history/HistoryList.tsx b/webapp/src/dogma/features/history/HistoryList.tsx
index 4339052619..6503db49ff 100644
--- a/webapp/src/dogma/features/history/HistoryList.tsx
+++ b/webapp/src/dogma/features/history/HistoryList.tsx
@@ -3,10 +3,8 @@ import { HistoryDto } from 'dogma/features/history/HistoryDto';
import { Badge, Box, Button, HStack, Icon } from '@chakra-ui/react';
import { ChakraLink } from 'dogma/common/components/ChakraLink';
import { DateWithTooltip } from 'dogma/common/components/DateWithTooltip';
-import { useMemo, useState } from 'react';
-import { useGetHistoryQuery } from 'dogma/features/api/apiSlice';
+import { ReactElement, useMemo } from 'react';
import { DynamicDataTable } from 'dogma/common/components/table/DynamicDataTable';
-import { Deferred } from 'dogma/common/components/Deferred';
import { Author } from 'dogma/common/components/Author';
import { GoCodescan } from 'react-icons/go';
import { VscGitCommit } from 'react-icons/vsc';
@@ -16,12 +14,25 @@ export type HistoryListProps = {
projectName: string;
repoName: string;
filePath: string;
+ data: HistoryDto[];
+ pagination: PaginationState;
+ setPagination: (updater: (old: PaginationState) => PaginationState) => void;
+ pageCount: number;
+ onEmptyData?: ReactElement;
isDirectory: boolean;
- totalRevision: number;
};
-const HistoryList = ({ projectName, repoName, filePath, isDirectory, totalRevision }: HistoryListProps) => {
- console.log('reload');
+const HistoryList = ({
+ projectName,
+ repoName,
+ filePath,
+ data,
+ pagination,
+ setPagination,
+ pageCount,
+ onEmptyData,
+ isDirectory,
+}: HistoryListProps) => {
const columnHelper = createColumnHelper();
const columns = useMemo(
() => [
@@ -46,15 +57,17 @@ const HistoryList = ({ projectName, repoName, filePath, isDirectory, totalRevisi
columnHelper.accessor((row: HistoryDto) => row.commitMessage.detail, {
cell: (info) => (
- }
- size={'sm'}
- colorScheme={'blue'}
- href={`/app/projects/${projectName}/repos/${repoName}/tree/${info.row.original.revision}${filePath}`}
- >
- Browse
-
+ {isDirectory ? (
+ }
+ size={'sm'}
+ colorScheme={'blue'}
+ href={`/app/projects/${projectName}/repos/${repoName}/tree/${info.row.original.revision}${filePath}`}
+ >
+ Browse
+
+ ) : null}
({
- pageIndex: 0,
- pageSize: 10,
- });
- const pagination = useMemo(
- () => ({
- pageIndex,
- pageSize,
- }),
- [pageIndex, pageSize],
- );
-
- const targetPath = isDirectory ? `${filePath}/**` : filePath;
- const { data, isLoading, error } = useGetHistoryQuery({
- projectName,
- repoName,
- filePath: targetPath,
- // revision starts from -1, for example for pageSize=20
- // The first page /projects/{projectName}/repos/{repoName}/commits/-1?to=-20
- // The second page /projects/{projectName}/repos/{repoName}/commits/-20?to=-40
- revision: -pageIndex * pageSize - 1,
- to: Math.max(-totalRevision, -(pageIndex + 1) * pageSize),
- });
-
return (
-
- {() => (
-
- )}
-
+
);
};
diff --git a/webapp/src/dogma/features/services/ErrorMessageParser.ts b/webapp/src/dogma/features/services/ErrorMessageParser.ts
index c93f50822b..e7ff53dbb5 100644
--- a/webapp/src/dogma/features/services/ErrorMessageParser.ts
+++ b/webapp/src/dogma/features/services/ErrorMessageParser.ts
@@ -1,5 +1,5 @@
class ErrorMessageParser {
- // eslint-disable-line @typescript-eslint/no-explicit-any
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
static parse(object: any): string {
if (object.response && object.response.data.message) {
return object.response.data.message;
diff --git a/webapp/src/dogma/util/path-util.ts b/webapp/src/dogma/util/path-util.ts
index ac14c9274e..b3ea72d56c 100644
--- a/webapp/src/dogma/util/path-util.ts
+++ b/webapp/src/dogma/util/path-util.ts
@@ -30,11 +30,16 @@ export type UrlAndSegment = {
url: string;
};
-export function makeTraversalFileLinks(projectName: string, repoName: string, path: string): UrlAndSegment[] {
+export function makeTraversalFileLinks(
+ projectName: string,
+ repoName: string,
+ infix: string,
+ path: string,
+): UrlAndSegment[] {
const links: UrlAndSegment[] = [];
const segments = path.split('/');
for (let i = 1; i < segments.length; i++) {
- const url = `/app/projects/${projectName}/repos/${repoName}/tree/head/${segments.slice(1, i + 1).join('/')}`;
+ const url = `/app/projects/${projectName}/repos/${repoName}/${infix}/${segments.slice(1, i + 1).join('/')}`;
links.push({ segment: segments[i], url });
}
return links;
diff --git a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/commit/[revision]/[[...path]]/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/commit/[revision]/[[...path]]/index.tsx
index 82a4d76c5e..d079d11ab6 100644
--- a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/commit/[revision]/[[...path]]/index.tsx
+++ b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/commit/[revision]/[[...path]]/index.tsx
@@ -83,7 +83,7 @@ const CommitViewPage = () => {
data: oldData,
isLoading: isOldLoading,
error: oldError,
- }: any = useGetFilesQuery(
+ } = useGetFilesQuery(
{
projectName,
repoName,
@@ -114,7 +114,8 @@ const CommitViewPage = () => {
return ;
}
// 404 Not Found is returned if the file does not exist in the old revision.
- const oldError0 = oldError && oldError.status != 404 ? oldError : null;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const oldError0 = oldError && (oldError as any).status != 404 ? oldError : null;
return (
{
const router = useRouter();
const repoName = router.query.repoName ? (router.query.repoName as string) : '';
const projectName = router.query.projectName ? (router.query.projectName as string) : '';
- const filePath = router.query.path ? `/${Array.from(router.query.path).join('/')}` : '';
+ const filePath = router.query.path ? toFilePath(router.query.path) : '';
+ const from = parseInt(router.query.from as string) || -1;
const directoryPath = router.asPath;
+ let type = router.query.type as string;
+ if (!type && !filePath) {
+ type = 'tree';
+ }
+ const targetPath = type == 'tree' ? `${filePath}/**` : filePath;
const { data, isLoading, error } = useGetNormalisedRevisionQuery({ projectName, repoName, revision: -1 });
+ const headRevision = data?.revision || -1;
+ let fromRevision = from;
+ if (from <= -1) {
+ fromRevision = headRevision + from + 1;
+ }
+ let baseRevision = parseInt(router.query.base as string) || fromRevision - DEFAULT_PAGE_SIZE + 1;
+ const pageSize = fromRevision - baseRevision + 1;
+ if (baseRevision < 1) {
+ baseRevision = 1;
+ }
+
+ const fromPageCount = Math.ceil(fromRevision / pageSize);
+ let headPageCount;
+ if (fromRevision > headRevision) {
+ headPageCount = 0;
+ } else {
+ headPageCount = Math.ceil((headRevision - fromRevision) / pageSize);
+ }
+
+ const pageCount = headPageCount + fromPageCount;
+ const pageIndex = headPageCount;
+ const pagination = { pageIndex, pageSize };
+ function setPagination(updater: (old: PaginationState) => PaginationState): void {
+ if (headRevision <= 0) {
+ return;
+ }
+
+ const newPagination = updater({ pageIndex, pageSize });
+ let newPageSize = newPagination.pageSize;
+ if (newPageSize == -1) {
+ newPageSize = pageSize;
+ }
+ const newPageIndex = newPagination.pageIndex;
+
+ if (newPageIndex != pageIndex || newPageSize != pageSize) {
+ const from = fromRevision - (newPageIndex - pageIndex) * newPageSize;
+ const base = from - newPageSize + 1;
+ const query: { [key: string]: string | number } = { from, base };
+ if (type) {
+ query['type'] = type;
+ }
+ router.push({
+ pathname: `/app/projects/${projectName}/repos/${repoName}/commits/${filePath}`,
+ query,
+ });
+ return;
+ }
+ }
+
+ const historyFrom = Math.min(fromRevision, headRevision);
+ const historyTo = Math.max(baseRevision, 1);
+ const {
+ data: historyData,
+ isLoading: isHistoryLoading,
+ error: historyError,
+ } = useGetHistoryQuery(
+ {
+ projectName,
+ repoName,
+ filePath: targetPath,
+ revision: historyFrom,
+ to: historyTo,
+ maxCommits: historyFrom - historyTo + 1,
+ },
+ {
+ skip: headRevision === -1 || historyFrom < historyTo,
+ },
+ );
+
+ const onEmptyData = (
+
+
+
+
+ No changes detected for {filePath} in range [{baseRevision}..{fromRevision}]
+
+
+
+
+ );
+
+ const urlAndSegments = makeTraversalFileLinks(projectName, repoName, 'commits', filePath);
return (
-
- {() => (
-
-
-
-
- {filePath ? (
-
-
-
-
- {makeTraversalFileLinks(projectName, repoName, filePath).map(({ segment, url }) => {
- return (
-
- {'/'}
- {segment}
-
- );
- })}
- commits
-
- ) : (
-
-
-
-
-
- {repoName}
-
- {filePath} commits
-
- )}
-
-
-
-
- )}
+
+ {() => {
+ const omitQueryList = [1, 2, 4, 5];
+ if (type !== 'tree' && router.query.from) {
+ // Omit the 'type=tree' query parameter when the type is a file.
+ omitQueryList.push(-2);
+ }
+ return (
+
+
+
+
+ {filePath ? (
+
+
+ {type === 'tree' ? : }
+
+ {urlAndSegments.map(({ segment, url }, index) => {
+ let query = '';
+ if (type === 'tree' || index < urlAndSegments.length - 1) {
+ query = '?type=tree';
+ }
+ const targetUrl = url + query;
+ return (
+
+ {'/'}
+ {segment}
+
+ );
+ })}
+ commits
+
+ ) : (
+
+
+
+
+
+
+ {repoName}
+
+
+ {filePath} commits
+
+ )}
+
+
+
+
+ );
+ }}
);
};
diff --git a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/compare/[revision]/base/[baseRevision]/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/compare/[revision]/base/[baseRevision]/index.tsx
index ebbcdb5ad3..37ec2fb017 100644
--- a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/compare/[revision]/base/[baseRevision]/index.tsx
+++ b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/compare/[revision]/base/[baseRevision]/index.tsx
@@ -14,7 +14,7 @@
* under the License.
*/
-import { useRouter } from 'next/router';
+import Router, { useRouter } from 'next/router';
import { useGetFilesQuery } from 'dogma/features/api/apiSlice';
import { Deferred } from 'dogma/common/components/Deferred';
import {
@@ -30,13 +30,17 @@ import {
} from '@chakra-ui/react';
import React, { useState } from 'react';
import { Breadcrumbs } from 'dogma/common/components/Breadcrumbs';
-import { ChakraLink } from 'dogma/common/components/ChakraLink';
import FourOhFour from 'pages/404';
-import { toFilePath } from 'dogma/util/path-util';
import { FaArrowLeftLong, FaCodeCompare } from 'react-icons/fa6';
import { GoDiff } from 'react-icons/go';
import DiffView, { DiffMode } from 'dogma/common/components/DiffView';
import DiffModeButton from 'dogma/common/components/DiffModeButton';
+import { useForm } from 'react-hook-form';
+
+type FormData = {
+ baseRevision: number;
+ headRevision: number;
+};
const ChangesViewPage = () => {
const router = useRouter();
@@ -44,10 +48,7 @@ const ChangesViewPage = () => {
const repoName = router.query.repoName as string;
const headRevision = parseInt(router.query.revision as string);
const baseRevision = parseInt(router.query.baseRevision as string);
- let filePath = toFilePath(router.query.path);
- if (filePath == '/') {
- filePath = '/**';
- }
+ const filePath = '/**';
const {
data: newData,
@@ -64,7 +65,7 @@ const ChangesViewPage = () => {
data: oldData,
isLoading: isOldLoading,
error: oldError,
- }: any = useGetFilesQuery({
+ } = useGetFilesQuery({
projectName,
repoName,
revision: baseRevision,
@@ -74,8 +75,22 @@ const ChangesViewPage = () => {
const { colorMode } = useColorMode();
const [diffMode, setDiffMode] = useState('Split');
- const [headRev, setHeadRev] = useState(headRevision);
- const [baseRev, setBaseRev] = useState(baseRevision);
+
+ const {
+ register,
+ handleSubmit,
+ formState: { isDirty },
+ } = useForm({
+ defaultValues: {
+ baseRevision: baseRevision,
+ headRevision: headRevision,
+ },
+ });
+ const onSubmit = (data: FormData) => {
+ Router.push(
+ `/app/projects/${projectName}/repos/${repoName}/compare/${data.headRevision}/base/${data.baseRevision}`,
+ );
+ };
if (headRevision <= 1) {
return ;
@@ -97,50 +112,49 @@ const ChangesViewPage = () => {
-
-
-
-
- Base
-
-
+
+
+
+
+ Base
+
+
+
+
+
+
+
+
+
+
+ From
+
+
+
+
+
+
-
-
-
-
-
-
-
- From
-
- setHeadRev(parseInt(e.target.value))}
- />
-
-
-
- }
- colorScheme="green"
- href={`/app/projects/${projectName}/repos/${repoName}/compare/${headRev}/base/${baseRev}`}
- >
- Compare
-
-
-
+ leftIcon={}
+ colorScheme="green"
+ isDisabled={!isDirty}
+ >
+ Compare
+
+
+
+
setDiffMode(value as DiffMode)} />
@@ -160,5 +174,4 @@ const ChangesViewPage = () => {
);
};
-
export default ChangesViewPage;
diff --git a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/files/[revision]/[[...path]]/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/files/[revision]/[[...path]]/index.tsx
index 9a1f648dde..1adbb62b40 100644
--- a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/files/[revision]/[[...path]]/index.tsx
+++ b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/files/[revision]/[[...path]]/index.tsx
@@ -1,6 +1,6 @@
import { InfoIcon } from '@chakra-ui/icons';
import { Box, Flex, Heading, HStack, Tag, Tooltip } from '@chakra-ui/react';
-import { useGetFileContentQuery, useGetHistoryQuery } from 'dogma/features/api/apiSlice';
+import { useGetFileContentQuery } from 'dogma/features/api/apiSlice';
import { useRouter } from 'next/router';
import FileEditor from 'dogma/common/components/editor/FileEditor';
import { Breadcrumbs } from 'dogma/common/components/Breadcrumbs';
@@ -27,20 +27,8 @@ const FileContentPage = () => {
refetchOnMountOrArgChange: true,
},
);
- const {
- data: historyData,
- isLoading: isHistoryLoading,
- error: historyError,
- } = useGetHistoryQuery({
- projectName,
- repoName,
- revision,
- to: 1,
- filePath,
- maxCommits: 1,
- });
return (
-
+
{() => {
return (
@@ -53,7 +41,7 @@ const FileContentPage = () => {
-
+
{fileName}
@@ -72,7 +60,7 @@ const FileContentPage = () => {
originalContent={data.content}
path={data.path}
name={fileName}
- commitRevision={historyData[0].revision}
+ revision={revision}
/>
);
diff --git a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/tree/[revision]/[[...path]]/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/tree/[revision]/[[...path]]/index.tsx
index 9fbc12797d..d49cafd0dd 100644
--- a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/tree/[revision]/[[...path]]/index.tsx
+++ b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/tree/[revision]/[[...path]]/index.tsx
@@ -18,14 +18,14 @@ import { GoRepo } from 'react-icons/go';
import { ChakraLink } from 'dogma/common/components/ChakraLink';
import { WithProjectRole } from 'dogma/features/auth/ProjectRole';
import { FaHistory } from 'react-icons/fa';
-import { makeTraversalFileLinks } from 'dogma/util/path-util';
+import { makeTraversalFileLinks, toFilePath } from 'dogma/util/path-util';
const RepositoryDetailPage = () => {
const router = useRouter();
const repoName = router.query.repoName ? (router.query.repoName as string) : '';
const projectName = router.query.projectName ? (router.query.projectName as string) : '';
const revision = router.query.revision ? (router.query.revision as string) : 'head';
- const filePath = router.query.path ? `/${Array.from(router.query.path).join('/')}` : '';
+ const filePath = router.query.path ? toFilePath(router.query.path) : '';
const directoryPath = router.asPath;
const dispatch = useAppDispatch();
@@ -108,14 +108,16 @@ cat ${project}/${repo}${path}`;
- {makeTraversalFileLinks(projectName, repoName, filePath).map(({ segment, url }) => {
- return (
-
- {'/'}
- {segment}
-
- );
- })}
+ {makeTraversalFileLinks(projectName, repoName, 'tree/head', filePath).map(
+ ({ segment, url }) => {
+ return (
+
+ {'/'}
+ {segment}
+
+ );
+ },
+ )}
) : (
@@ -138,7 +140,7 @@ cat ${project}/${repo}${path}`;
}
variant="outline"
colorScheme="gray"
|