2021-12-12 23:56:12 +01:00
|
|
|
/*
|
|
|
|
|
Copyright (c) Microsoft Corporation.
|
|
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
2022-09-21 03:41:51 +02:00
|
|
|
import type { TestAttachment } from './types';
|
2021-12-12 23:56:12 +01:00
|
|
|
import * as React from 'react';
|
2021-12-14 00:37:01 +01:00
|
|
|
import * as icons from './icons';
|
2021-12-12 23:56:12 +01:00
|
|
|
import { TreeItem } from './treeItem';
|
2023-10-12 02:56:05 +02:00
|
|
|
import { CopyToClipboard } from './copyToClipboard';
|
2021-12-12 23:56:12 +01:00
|
|
|
import './links.css';
|
2024-08-01 12:43:29 +02:00
|
|
|
import { linkifyText } from '@web/renderUtils';
|
2024-07-31 12:12:06 +02:00
|
|
|
import { clsx } from '@web/uiUtils';
|
2021-12-12 23:56:12 +01:00
|
|
|
|
2021-12-14 00:37:01 +01:00
|
|
|
export function navigate(href: string) {
|
|
|
|
|
window.history.pushState({}, '', href);
|
|
|
|
|
const navEvent = new PopStateEvent('popstate');
|
|
|
|
|
window.dispatchEvent(navEvent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const Route: React.FunctionComponent<{
|
2022-07-12 04:47:15 +02:00
|
|
|
predicate: (params: URLSearchParams) => boolean,
|
2021-12-14 00:37:01 +01:00
|
|
|
children: any
|
2022-07-12 04:47:15 +02:00
|
|
|
}> = ({ predicate, children }) => {
|
2024-10-30 02:29:07 +01:00
|
|
|
const searchParams = React.useContext(SearchParamsContext);
|
|
|
|
|
return predicate(searchParams) ? children : null;
|
2021-12-14 00:37:01 +01:00
|
|
|
};
|
|
|
|
|
|
2021-12-12 23:56:12 +01:00
|
|
|
export const Link: React.FunctionComponent<{
|
2024-04-26 19:50:20 +02:00
|
|
|
href?: string,
|
|
|
|
|
click?: string,
|
|
|
|
|
ctrlClick?: string,
|
2021-12-12 23:56:12 +01:00
|
|
|
className?: string,
|
|
|
|
|
title?: string,
|
|
|
|
|
children: any,
|
2024-07-31 12:12:06 +02:00
|
|
|
}> = ({ click, ctrlClick, children, ...rest }) => {
|
|
|
|
|
return <a {...rest} style={{ textDecoration: 'none', color: 'var(--color-fg-default)', cursor: 'pointer' }} onClick={e => {
|
2024-04-26 19:50:20 +02:00
|
|
|
if (click) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
navigate(e.metaKey || e.ctrlKey ? ctrlClick || click : click);
|
|
|
|
|
}
|
|
|
|
|
}}>{children}</a>;
|
2021-12-12 23:56:12 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const ProjectLink: React.FunctionComponent<{
|
2021-12-15 04:25:07 +01:00
|
|
|
projectNames: string[],
|
2021-12-12 23:56:12 +01:00
|
|
|
projectName: string,
|
2021-12-15 04:25:07 +01:00
|
|
|
}> = ({ projectNames, projectName }) => {
|
2021-12-12 23:56:12 +01:00
|
|
|
const encoded = encodeURIComponent(projectName);
|
|
|
|
|
const value = projectName === encoded ? projectName : `"${encoded.replace(/%22/g, '%5C%22')}"`;
|
|
|
|
|
return <Link href={`#?q=p:${value}`}>
|
2024-07-31 12:12:06 +02:00
|
|
|
<span className={clsx('label', `label-color-${projectNames.indexOf(projectName) % 6}`)} style={{ margin: '6px 0 0 6px' }}>
|
2021-12-12 23:56:12 +01:00
|
|
|
{projectName}
|
|
|
|
|
</span>
|
|
|
|
|
</Link>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const AttachmentLink: React.FunctionComponent<{
|
|
|
|
|
attachment: TestAttachment,
|
|
|
|
|
href?: string,
|
2022-02-16 18:09:42 +01:00
|
|
|
linkName?: string,
|
2024-09-02 08:35:53 +02:00
|
|
|
openInNewTab?: boolean,
|
|
|
|
|
}> = ({ attachment, href, linkName, openInNewTab }) => {
|
2021-12-12 23:56:12 +01:00
|
|
|
return <TreeItem title={<span>
|
2021-12-14 00:37:01 +01:00
|
|
|
{attachment.contentType === kMissingContentType ? icons.warning() : icons.attachment()}
|
2023-08-16 18:06:04 +02:00
|
|
|
{attachment.path && <a href={href || attachment.path} download={downloadFileNameForAttachment(attachment)}>{linkName || attachment.name}</a>}
|
2024-09-02 08:35:53 +02:00
|
|
|
{!attachment.path && (
|
|
|
|
|
openInNewTab
|
|
|
|
|
? <a href={URL.createObjectURL(new Blob([attachment.body!], { type: attachment.contentType }))} target='_blank' rel='noreferrer' onClick={e => e.stopPropagation()}>{attachment.name}</a>
|
|
|
|
|
: <span>{linkifyText(attachment.name)}</span>
|
|
|
|
|
)}
|
2021-12-12 23:56:12 +01:00
|
|
|
</span>} loadChildren={attachment.body ? () => {
|
2024-08-20 14:16:28 +02:00
|
|
|
return [<div key={1} className='attachment-body'><CopyToClipboard value={attachment.body!}/>{linkifyText(attachment.body!)}</div>];
|
2022-03-31 23:11:34 +02:00
|
|
|
} : undefined} depth={0} style={{ lineHeight: '32px' }}></TreeItem>;
|
2021-12-12 23:56:12 +01:00
|
|
|
};
|
|
|
|
|
|
2024-10-30 02:29:07 +01:00
|
|
|
export const SearchParamsContext = React.createContext<URLSearchParams>(new URLSearchParams(window.location.hash.slice(1)));
|
|
|
|
|
|
|
|
|
|
export const SearchParamsProvider: React.FunctionComponent<React.PropsWithChildren> = ({ children }) => {
|
|
|
|
|
const [searchParams, setSearchParams] = React.useState<URLSearchParams>(new URLSearchParams(window.location.hash.slice(1)));
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
const listener = () => setSearchParams(new URLSearchParams(window.location.hash.slice(1)));
|
|
|
|
|
window.addEventListener('popstate', listener);
|
|
|
|
|
return () => window.removeEventListener('popstate', listener);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return <SearchParamsContext.Provider value={searchParams}>{children}</SearchParamsContext.Provider>;
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-16 18:06:04 +02:00
|
|
|
function downloadFileNameForAttachment(attachment: TestAttachment): string {
|
|
|
|
|
if (attachment.name.includes('.') || !attachment.path)
|
|
|
|
|
return attachment.name;
|
|
|
|
|
const firstDotIndex = attachment.path.indexOf('.');
|
|
|
|
|
if (firstDotIndex === -1)
|
|
|
|
|
return attachment.name;
|
|
|
|
|
return attachment.name + attachment.path.slice(firstDotIndex, attachment.path.length);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 04:47:15 +02:00
|
|
|
export function generateTraceUrl(traces: TestAttachment[]) {
|
|
|
|
|
return `trace/index.html?${traces.map((a, i) => `trace=${new URL(a.path!, window.location.href)}`).join('&')}`;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-12 23:56:12 +01:00
|
|
|
const kMissingContentType = 'x-playwright/missing';
|