react-copy-to-clipboard#CopyToClipboard TypeScript Examples
The following examples show how to use
react-copy-to-clipboard#CopyToClipboard.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: [id].tsx From livepeer-com with MIT License | 6 votes |
ClipBut = ({ text }) => {
const [isCopied, setCopied] = useState(0);
useEffect(() => {
if (isCopied) {
const timeout = setTimeout(() => {
setCopied(0);
}, isCopied);
return () => clearTimeout(timeout);
}
}, [isCopied]);
return (
<CopyToClipboard text={text} onCopy={() => setCopied(2000)}>
<Flex
sx={{
alignItems: "center",
cursor: "pointer",
ml: 0,
mr: 0,
}}>
<Box sx={{ mr: 2 }}>{text}</Box>
<Copy
sx={{
mr: 1,
width: 14,
height: 14,
color: "offBlack",
}}
/>
{!!isCopied && (
<Box sx={{ fontSize: 12, color: "offBlack" }}>Copied</Box>
)}
</Flex>
</CopyToClipboard>
);
}
Example #2
Source File: index.tsx From ever-wallet-browser-extension with GNU General Public License v3.0 | 6 votes |
export function CopyButton({ children, id = 'copy-button', text }: Props): JSX.Element {
const intl = useIntl()
return (
<div
data-tip={intl.formatMessage({ id: 'COPIED_TOOLTIP' })}
data-for={id}
data-event="click focus"
>
<CopyToClipboard text={text}>{children}</CopyToClipboard>
<ReactTooltip id={id} type="dark" effect="solid" place="top" />
</div>
)
}
Example #3
Source File: index.tsx From crust-apps with Apache License 2.0 | 6 votes |
CopyButton:React.FC<Props> = ({ text, children, onCopy, message }) => {
const { t } = useTranslation("order");
const { queueAction } = useContext(StatusContext);
const _onCopy = useCallback(() => {
onCopy && onCopy();
queueAction &&
queueAction({
action: t("clipboard"),
message,
status: "queued"
});
}, [queueAction, t]);
return (
<CopyToClipboard text={text.toString()} onCopy={_onCopy}>
{children}
</CopyToClipboard>
);
}
Example #4
Source File: shareDeal.component.tsx From filecoin-CID-checker with Apache License 2.0 | 6 votes |
ShareDeal = (props: Props) => {
const { text, className, title = 'Click to copy Deal link to share' } = props
return (
<CopyToClipboard text={`${window.location.origin}/deal/${text}`}>
<ShareWrapper className={className} title={title}>
<ShareImageWrapper />
</ShareWrapper>
</CopyToClipboard>
)
}
Example #5
Source File: copyText.component.tsx From filecoin-CID-checker with Apache License 2.0 | 6 votes |
CopyText = (props: Props) => {
const { text, className, title = 'Click to copy' } = props
return (
<div onClick={(e: any) => e.stopPropagation()}>
<CopyToClipboard onClick={(e: any) => e.stopPropagation()} text={text}>
<CopyWrapper className={className} title={title}>
<CopyImageWrapper />
</CopyWrapper>
</CopyToClipboard>
</div>
)
}
Example #6
Source File: utils.tsx From NekoMaid with MIT License | 6 votes |
ParsedComponent: React.FC<{ component: TextComponent, runCommand?: (it: string) => void, suggest?: (it: string) => void }> =
({ component: it, runCommand, suggest }) => {
let className: string | undefined
const style: any = { }
if (it.bold) style.fontWeight = 'bold'
if (it.italic) style.fontStyle = 'italic'
if (it.underlined) style.textDecoration = 'underline'
if (it.strikethrough) style.textDecoration = (style.textDecoration ? style.textDecoration + ' ' : '') + 'line-through'
let color = it.color
if (typeof color === 'string') color = parseStringColor(color)
if (color?.name && color.name in colorsMap) {
style.color = colors[colorsMap[color.name]]
if (color.name === 'white' || color.name === 'black') className = color.name
} else if (color?.color) style.color = `rgba(${color.color.r},${color.color.g},${color.color.b},${color.color.alpha})`
if (style.color && !(color?.name === 'white' || color?.name === 'black')) style.textShadow = 'none'
if (it.clickEvent) style.cursor = 'pointer'
let content: any
if (it.translate) {
if (it.translate in minecraft) {
let i = 0
const arr = it.with || []
content = minecraft[it.translate].split('%').map((str, j) => {
let comp: any
if (j) {
if (str[0] === 's') {
comp = arr[i++]
str = str.slice(1)
} else {
const id = parseInt(str)
if (id > 0) {
comp = arr[id - 1]
str = str.slice(id.toString().length + 2)
}
}
}
return <>{comp && parseStringOrComponent(comp)}{str}</>
})
} else content = it.text ? parseMessage(it.text) : it.translate
} else if (it.text) content = parseMessage(it.text)
let elm = <span
style={style}
className={className}
onClick={it.clickEvent
? () => {
const value = it.clickEvent!.value
switch (it.clickEvent!.action) {
case 'OPEN_URL': return window.open(value, '_blank')
case 'RUN_COMMAND': return runCommand && runCommand(value.slice(1))
case 'SUGGEST_COMMAND': return suggest && suggest(value.slice(1))
}
}
: undefined
}
>{content}{it.extra && parseComponents(it.extra, runCommand, suggest)}</span>
if (it.hoverEvent?.action === 'SHOW_TEXT' && it.hoverEvent.contents.value) {
elm = <Tooltip title={<>{parseComponents(it.hoverEvent.contents.value, runCommand, suggest)}</>}>{elm}</Tooltip>
}
return it.clickEvent?.action === 'COPY_TO_CLIPBOARD' ? <CopyToClipboard text={it.clickEvent!.value}>{elm}</CopyToClipboard> : elm
}
Example #7
Source File: ClipboardCopy.tsx From bee-dashboard with BSD 3-Clause "New" or "Revised" License | 6 votes |
export default function ClipboardCopy({ value }: Props): ReactElement {
const { enqueueSnackbar } = useSnackbar()
const handleCopy = () => enqueueSnackbar(`Copied: ${value}`, { variant: 'success' })
return (
<div style={{ marginRight: '3px', marginLeft: '3px' }}>
<IconButton color="primary" size="small" onClick={handleCopy}>
<CopyToClipboard text={value}>
<Clipboard style={{ height: '20px' }} />
</CopyToClipboard>
</IconButton>
</div>
)
}
Example #8
Source File: CodeCopy.tsx From frontend with Apache License 2.0 | 6 votes |
CodeCopy: FunctionComponent<Props> = ({ text, onCopy, className, nested }) => {
const { t } = useTranslation()
const [copied, setCopied] = useState(false)
useEffect(() => {
const timeout = setTimeout(() => {
if (copied) setCopied(false);
}, 1200);
return () => clearTimeout(timeout);
}, [copied]);
return (< div className={`${styles.pre} ${className} ${nested ? styles.nested : ''}`}>
{text}
<CopyToClipboard text={text} onCopy={() => {
setCopied(true)
onCopy()
}}>
<button className={styles.copy} title={t('copy-text')}>
{!copied && <MdContentCopy></MdContentCopy>}
{copied && <MdCheck style={{ 'color': "green" }}></MdCheck>}
</button>
</CopyToClipboard >
</div >
)
}
Example #9
Source File: streamDetail.tsx From livepeer-com with MIT License | 5 votes |
ShowURL = ({ url, shortendUrl, anchor = false }: ShowURLProps) => {
const [isCopied, setCopied] = useState(0);
const [openSnackbar] = useSnackbar();
useEffect(() => {
if (isCopied) {
const interval = setTimeout(() => {
setCopied(0);
}, isCopied);
return () => clearTimeout(interval);
}
}, [isCopied]);
return (
<HoverCardRoot openDelay={200}>
<HoverCardTrigger>
<Flex css={{ ai: "center" }}>
<CopyToClipboard
text={url}
onCopy={() => {
openSnackbar("Copied to clipboard");
setCopied(2000);
}}>
<Flex
css={{
alignItems: "center",
cursor: "pointer",
ml: 0,
mr: 0,
}}>
{anchor ? (
<A
css={{ fontSize: "$2", mr: "$1" }}
href={url}
target="_blank">
{shortendUrl ? shortendUrl : url}
</A>
) : (
<Box css={{ fontSize: "$2", mr: "$1" }}>
{shortendUrl ? shortendUrl : url}
</Box>
)}
<Copy
css={{
mr: "$2",
width: 14,
height: 14,
color: "$hiContrast",
}}
/>
</Flex>
</CopyToClipboard>
</Flex>
</HoverCardTrigger>
<HoverCardContent>
<Text
variant="gray"
css={{
backgroundColor: "$panel",
borderRadius: 6,
px: "$3",
py: "$1",
fontSize: "$1",
display: "flex",
ai: "center",
}}>
<Box>{isCopied ? "Copied" : "Copy to Clipboard"}</Box>
</Text>
</HoverCardContent>
</HoverCardRoot>
);
}
Example #10
Source File: CreateTokenDialog.tsx From livepeer-com with MIT License | 5 votes |
ClipBut = ({ text }) => {
const [isCopied, setCopied] = useState(0);
const [openSnackbar] = useSnackbar();
useEffect(() => {
if (isCopied) {
const timeout = setTimeout(() => {
setCopied(0);
}, isCopied);
return () => clearTimeout(timeout);
}
}, [isCopied]);
return (
<HoverCardRoot openDelay={200}>
<HoverCardTrigger>
<Flex css={{ height: 25, ai: "center" }}>
<CopyToClipboard
text={text}
onCopy={() => {
openSnackbar("Copied to clipboard");
setCopied(2000);
}}>
<Flex
css={{
alignItems: "center",
cursor: "pointer",
ml: 0,
mr: 0,
}}>
<Box css={{ mr: "$1" }}>{text}</Box>
<Copy
css={{
mr: "$2",
width: 14,
height: 14,
color: "$hiContrast",
}}
/>
</Flex>
</CopyToClipboard>
</Flex>
</HoverCardTrigger>
<HoverCardContent>
<Text
variant="gray"
css={{
backgroundColor: "$panel",
borderRadius: 6,
px: "$3",
py: "$1",
fontSize: "$1",
display: "flex",
ai: "center",
}}>
<Box>{isCopied ? "Copied" : "Copy to Clipboard"}</Box>
</Text>
</HoverCardContent>
</HoverCardRoot>
);
}
Example #11
Source File: streamDetail.tsx From livepeer-com with MIT License | 5 votes |
ClipBut = ({ text }) => {
const [isCopied, setCopied] = useState(0);
const [openSnackbar] = useSnackbar();
useEffect(() => {
if (isCopied) {
const timeout = setTimeout(() => {
setCopied(0);
}, isCopied);
return () => clearTimeout(timeout);
}
}, [isCopied]);
return (
<HoverCardRoot openDelay={200}>
<HoverCardTrigger>
<Flex css={{ ai: "center" }}>
<CopyToClipboard
text={text}
onCopy={() => {
openSnackbar("Copied to clipboard");
setCopied(2000);
}}>
<Flex
css={{
alignItems: "center",
cursor: "pointer",
ml: 0,
mr: 0,
}}>
<Box css={{ mr: "$1" }}>{text}</Box>
<Copy
css={{
mr: "$2",
width: 14,
height: 14,
color: "$hiContrast",
}}
/>
</Flex>
</CopyToClipboard>
</Flex>
</HoverCardTrigger>
<HoverCardContent>
<Text
variant="gray"
css={{
backgroundColor: "$panel",
borderRadius: 6,
px: "$3",
py: "$1",
fontSize: "$1",
display: "flex",
ai: "center",
}}>
<Box>{isCopied ? "Copied" : "Copy to Clipboard"}</Box>
</Text>
</HoverCardContent>
</HoverCardRoot>
);
}
Example #12
Source File: [id].tsx From livepeer-com with MIT License | 5 votes |
ShowURL = ({ text, url, urlToCopy, anchor = false }: ShowURLProps) => {
const [isCopied, setCopied] = useState(0);
useEffect(() => {
if (isCopied) {
const interval = setTimeout(() => {
setCopied(0);
}, isCopied);
return () => clearTimeout(interval);
}
}, [isCopied]);
const ccurl = urlToCopy ? urlToCopy : url;
return (
<Flex sx={{ justifyContent: "flex-start", alignItems: "center" }}>
{text ? (
<Box sx={{ minWidth: 125, fontSize: 12, paddingRight: "1em" }}>
{text}:
</Box>
) : null}
<CopyToClipboard text={ccurl} onCopy={() => setCopied(2000)}>
<Flex sx={{ alignItems: "center" }}>
{anchor ? (
<Box
as="a"
sx={{ fontSize: 12, fontFamily: "monospace", mr: 1 }}
href={url}
target="_blank">
{url}
</Box>
) : (
<Box
as="span"
sx={{ fontSize: 12, fontFamily: "monospace", mr: 1 }}>
{url}
</Box>
)}
<Copy
sx={{
mr: 1,
cursor: "pointer",
width: 14,
height: 14,
color: "offBlack",
}}
/>
</Flex>
</CopyToClipboard>
{!!isCopied && <Box sx={{ fontSize: 12, color: "offBlack" }}>Copied</Box>}
</Flex>
);
}
Example #13
Source File: ExpandableListItemKey.tsx From bee-dashboard with BSD 3-Clause "New" or "Revised" License | 5 votes |
export default function ExpandableListItemKey({ label, value, expanded }: Props): ReactElement | null {
const classes = useStyles()
const [open, setOpen] = useState(expanded || false)
const [copied, setCopied] = useState(false)
const toggleOpen = () => setOpen(!open)
const tooltipClickHandler = () => setCopied(true)
const tooltipCloseHandler = () => setCopied(false)
const splitValues = split(value)
const hasPrefix = isPrefixedHexString(value)
const spanText = `${hasPrefix ? `${splitValues[0]} ${splitValues[1]}` : splitValues[0]}[…]${
splitValues[splitValues.length - 1]
}`
return (
<ListItem className={`${classes.header} ${open ? classes.headerOpen : ''}`}>
<Grid container direction="column" justifyContent="space-between" alignItems="stretch">
<Grid container direction="row" justifyContent="space-between" alignItems="center">
{label && <Typography variant="body1">{label}</Typography>}
<Typography variant="body2">
<div>
{!open && (
<span className={classes.copyValue}>
<Tooltip title={copied ? 'Copied' : 'Copy'} placement="top" arrow onClose={tooltipCloseHandler}>
<CopyToClipboard text={value}>
<span onClick={tooltipClickHandler}>{value ? spanText : ''}</span>
</CopyToClipboard>
</Tooltip>
</span>
)}
<IconButton size="small" className={classes.copyValue}>
{open ? <Minus onClick={toggleOpen} strokeWidth={1} /> : <Eye onClick={toggleOpen} strokeWidth={1} />}
</IconButton>
</div>
</Typography>
</Grid>
<Collapse in={open} timeout="auto" unmountOnExit>
<div className={classes.content}>
<Tooltip title={copied ? 'Copied' : 'Copy'} placement="top" arrow onClose={tooltipCloseHandler}>
<CopyToClipboard text={value}>
{/* This has to be wrapped in two spans otherwise either the tooltip or the highlighting does not work*/}
<span onClick={tooltipClickHandler}>
<span className={classes.copyValue}>
{splitValues.map((s, i) => (
<Typography variant="body2" key={i} className={classes.keyMargin} component="span">
{s}
</Typography>
))}
</span>
</span>
</CopyToClipboard>
</Tooltip>
</div>
</Collapse>
</Grid>
</ListItem>
)
}
Example #14
Source File: index.tsx From rabet-extension with GNU General Public License v3.0 | 5 votes |
CopyText = ({ text, custom, fullIcon }: AppProps) => {
const [visible, setVisible] = useState(false);
const [tooltipText, setTooltipText] = useState('Copy to clipboard');
const toggle = () => {
setTooltipText('Copied!');
setVisible(true);
};
const renderCopyTrigger = () => {
if (custom) {
return custom;
}
if (fullIcon) {
return (
<StyledButton type="button">
<Copy />
Copy
</StyledButton>
);
}
return <Copy />;
};
const onMouseEnter = () => setVisible(true);
const onMouseLeave = () => {
setVisible(false);
setTooltipText('Copy to clipboard');
};
return (
<span
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={toggle}
className="cursor-pointer"
>
<Tooltips
text={tooltipText}
placement="top"
isVisible={visible}
controlled
>
<CopyToClipboard text={text}>
{renderCopyTrigger()}
</CopyToClipboard>
</Tooltips>
</span>
);
}
Example #15
Source File: PublicToken.tsx From your_spotify with GNU General Public License v3.0 | 5 votes |
export default function PublicToken() {
const dispatch = useAppDispatch();
const user = useSelector(selectUser);
const location = window.location.origin;
const generate = useCallback(() => {
dispatch(generateNewPublicToken());
}, [dispatch]);
const onCopy = useCallback(() => {
dispatch(
alertMessage({
level: 'info',
message: 'Public url copied to clipboard',
}),
);
}, [dispatch]);
if (!user) {
return null;
}
const link = `${location}/?token=${user.publicToken}`;
return (
<div>
<Text element="div" className={s.disclaimer}>
The generated url will allow anyone with it to view your stats indefinitely. The user
won't be able to execute any action that modifies your account. Regenerating it will
cause the older link to be deprecated instantly. You can also share the page you're
currently viewing using the <code>Share this page</code> button on the side.
</Text>
<SettingLine
left="Your public token"
right={
user.publicToken ? (
<CopyToClipboard text={link} onCopy={onCopy}>
<div className={s.link}>
<Text element="div">{link}</Text>
</div>
</CopyToClipboard>
) : (
'No token generated'
)
}
/>
<SettingLine left="Regenerate" right={<Button onClick={generate}>Generate</Button>} />
</div>
);
}
Example #16
Source File: snackbar.tsx From rugenerous-frontend with MIT License | 5 votes |
SnackMessage = forwardRef<HTMLDivElement, { id: string | number; message: Message }>((props, ref) => {
const classes = useStyles();
const { closeSnackbar } = useSnackbar();
const [expanded, setExpanded] = useState(false);
const [isCopy, setIsCopy] = useState(false);
const handleExpandClick = useCallback(() => {
setExpanded(oldExpanded => !oldExpanded);
}, []);
const handleDismiss = useCallback(() => {
closeSnackbar(props.id);
}, [props.id, closeSnackbar]);
const getIcon = (severity: Color) => {
switch (severity) {
case "error":
return <ErrorIcon color="inherit" />;
case "info":
return <InfoIcon color="inherit" />;
case "success":
return <SuccessIcon color="inherit" />;
case "warning":
return <WarningIcon color="inherit" />;
default:
return <div />;
}
};
return (
<SnackbarContent ref={ref} className={classes.root}>
<Card className={classnames(classes.card, classes[props.message.severity])}>
<CardActions classes={{ root: classes.actionRoot }}>
{getIcon(props.message.severity)}
<Typography variant="subtitle2" className={classes.typography}>
{props.message.text}
</Typography>
<div className={classes.icons}>
{props.message.error && (
<IconButton
aria-label="Show more"
className={classnames(classes.expand, { [classes.expandOpen]: expanded })}
onClick={handleExpandClick}
>
<ExpandMoreIcon color="inherit" />
</IconButton>
)}
<IconButton className={classes.expand} onClick={handleDismiss}>
<CloseIcon color="inherit" />
</IconButton>
</div>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<Paper className={classes.collapse}>
<CopyToClipboard text={JSON.stringify(props.message.error)} onCopy={() => setIsCopy(true)}>
<Button size="small" className={classes.button}>
<CheckCircleIcon className={classnames(classes.checkIcon, { [classes.checkIconCopy]: isCopy })} />
Copy to clipboard
</Button>
</CopyToClipboard>
<div className={classes.errorWrap}>
<Typography>Error message: </Typography>
<Typography className={classes.errorText}>{JSON.stringify(props.message.error, null, 2)}</Typography>
</div>
</Paper>
</Collapse>
</Card>
</SnackbarContent>
);
})
Example #17
Source File: snackbar.tsx From wonderland-frontend with MIT License | 5 votes |
SnackMessage = forwardRef<HTMLDivElement, { id: string | number; message: Message }>((props, ref) => {
const classes = useStyles();
const { closeSnackbar } = useSnackbar();
const [expanded, setExpanded] = useState(false);
const [isCopy, setIsCopy] = useState(false);
const handleExpandClick = useCallback(() => {
setExpanded(oldExpanded => !oldExpanded);
}, []);
const handleDismiss = useCallback(() => {
closeSnackbar(props.id);
}, [props.id, closeSnackbar]);
const getIcon = (severity: Color) => {
switch (severity) {
case "error":
return <ErrorIcon color="inherit" />;
case "info":
return <InfoIcon color="inherit" />;
case "success":
return <SuccessIcon color="inherit" />;
case "warning":
return <WarningIcon color="inherit" />;
default:
return <div />;
}
};
return (
<SnackbarContent ref={ref} className={classes.root}>
<Card className={classnames(classes.card, classes[props.message.severity])}>
<CardActions classes={{ root: classes.actionRoot }}>
{getIcon(props.message.severity)}
<Typography variant="subtitle2" className={classes.typography}>
{props.message.text}
</Typography>
<div className={classes.icons}>
{props.message.error && (
<IconButton aria-label="Show more" className={classnames(classes.expand, { [classes.expandOpen]: expanded })} onClick={handleExpandClick}>
<ExpandMoreIcon color="inherit" />
</IconButton>
)}
<IconButton className={classes.expand} onClick={handleDismiss}>
<CloseIcon color="inherit" />
</IconButton>
</div>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<Paper className={classes.collapse}>
<CopyToClipboard text={JSON.stringify(props.message.error)} onCopy={() => setIsCopy(true)}>
<Button size="small" className={classes.button}>
<CheckCircleIcon className={classnames(classes.checkIcon, { [classes.checkIconCopy]: isCopy })} />
Copy to clipboard
</Button>
</CopyToClipboard>
<div className={classes.errorWrap}>
<Typography>Error message: </Typography>
<Typography className={classes.errorText}>{JSON.stringify(props.message.error, null, 2)}</Typography>
</div>
</Paper>
</Collapse>
</Card>
</SnackbarContent>
);
})
Example #18
Source File: [id].tsx From livepeer-com with MIT License | 4 votes |
ID = () => {
useLoggedIn();
const {
user,
logout,
getStreamInfo,
deleteStream,
getIngest,
patchStream,
getAdminStreams,
terminateStream,
} = useApi();
const userIsAdmin = user && user.admin;
const router = useRouter();
const { query } = router;
const id = query.id?.toString();
const [stream, setStream] = useState<Stream>(null);
const [streamOwner, setStreamOwner] = useState<User>(null);
const [ingest, setIngest] = useState([]);
const [deleteModal, setDeleteModal] = useState(false);
const [terminateModal, setTerminateModal] = useState(false);
const [suspendUserModal, setSuspendUserModal] = useState(false);
const [suspendModal, setSuspendModal] = useState(false);
const [notFound, setNotFound] = useState(false);
const [recordOffModal, setRecordOffModal] = useState(false);
const [isCopied, setCopied] = useState(0);
const [lastSession, setLastSession] = useState(null);
const [lastSessionLoading, setLastSessionLoading] = useState(false);
const [regionalUrlsVisible, setRegionalUrlsVisible] = useState(false);
const [resultText, setResultText] = useState("");
const [alertText, setAlertText] = useState("");
useEffect(() => {
if (user && user.admin && stream && !lastSessionLoading) {
setLastSessionLoading(true);
getAdminStreams({
sessionsonly: true,
limit: 1,
order: "createdAt-true",
filters: [{ id: "parentId", value: stream.id }],
userId: stream.userId,
})
.then((res) => {
const [streamsOrError] = res;
if (Array.isArray(streamsOrError) && streamsOrError.length > 0) {
setLastSession(streamsOrError[0]);
}
})
.catch((e) => console.log(e))
.finally(() => setLastSessionLoading(false));
}
}, [user, stream]);
useEffect(() => {
if (isCopied) {
const timeout = setTimeout(() => {
setCopied(0);
}, isCopied);
return () => clearTimeout(timeout);
}
}, [isCopied]);
useEffect(() => {
getIngest(true)
.then((ingest) => {
if (Array.isArray(ingest)) {
ingest.sort((a, b) => a.base.localeCompare(b.base));
}
setIngest(ingest);
})
.catch((err) => console.error(err)); // todo: surface this
}, [id]);
const fetchStream = useCallback(async () => {
if (!id) {
return;
}
try {
const [res, info] = await getStreamInfo(id);
if (res.status === 404) {
return setNotFound(true);
} else if ("errors" in info) {
throw new Error(info.errors.toString());
}
setStream(info.stream);
setStreamOwner(info.user);
} catch (err) {
console.error(err); // todo: surface this
}
}, [id]);
useEffect(() => {
fetchStream();
}, [fetchStream]);
const isVisible = usePageVisibility();
useEffect(() => {
if (!isVisible || notFound) {
return;
}
const interval = setInterval(fetchStream, 5000);
return () => clearInterval(interval);
}, [fetchStream, isVisible, notFound]);
const userField = useMemo(() => {
let value = streamOwner?.email;
if (streamOwner?.admin) {
value += " (admin)";
}
if (streamOwner?.suspended) {
value += " (suspended)";
}
return value;
}, [streamOwner?.email, streamOwner?.admin, streamOwner?.suspended]);
const playerUrl = useMemo(() => {
if (!stream?.playbackId) {
return "https://lvpr.tv/";
}
const autoplay = query.autoplay?.toString() ?? "0";
let url = `https://lvpr.tv/?theme=fantasy&live=${stream?.playbackId}&autoplay=${autoplay}`;
if (isStaging() || isDevelopment()) {
url += "&monster";
}
return url;
}, [query.autoplay, stream?.playbackId]);
const [keyRevealed, setKeyRevealed] = useState(false);
const close = () => {
setSuspendModal(false);
setTerminateModal(false);
setSuspendUserModal(false);
setDeleteModal(false);
setRecordOffModal(false);
};
if (!user) {
return <Layout />;
}
const getIngestURL = (
stream: Stream,
showKey: boolean,
i: number
): string => {
const key = showKey ? stream.streamKey : "";
return i < ingest.length ? pathJoin(ingest[i].ingest, key) : key || "";
};
const getPlaybackURL = (stream: Stream, i: number): string => {
return i < ingest.length
? pathJoin(ingest[i].base, "hls", `${stream.playbackId}/index.m3u8`)
: stream.playbackId || "";
};
const doSetRecord = async (stream: Stream, record: boolean) => {
console.log(`do set record ${stream.id} record ${record}`);
setStream(null); // shows 'loading wheel' immediately
await patchStream(stream.id, { record });
setStream(null); // make sure that we will load updated stream
};
const isAdmin = query.admin === "true";
const tabs = getTabsAdmin(2);
const backLink = isAdmin ? "/app/admin/streams" : "/app/user";
let { broadcasterHost, region } = stream || {};
if (!broadcasterHost && lastSession && lastSession.broadcasterHost) {
broadcasterHost = lastSession.broadcasterHost;
}
if (!region && lastSession && lastSession.region) {
region = lastSession.region;
}
let broadcasterPlaybackUrl;
const playbackId = (stream || {}).playbackId || "";
const domain = isStaging() ? "monster" : "com";
const globalIngestUrl = `rtmp://rtmp.livepeer.${domain}/live`;
const globalPlaybackUrl = `https://livepeercdn.${domain}/hls/${playbackId}/index.m3u8`;
if (stream && stream.region && !lastSession) {
broadcasterPlaybackUrl = `https://${stream.region}.livepeer.${domain}/stream/${stream.id}.m3u8`;
} else if (lastSession && lastSession.region) {
broadcasterPlaybackUrl = `https://${lastSession.region}.livepeer.${domain}/stream/${playbackId}.m3u8`;
}
return (
<TabbedLayout tabs={tabs} logout={logout}>
<Container>
{suspendModal && stream && (
<ConfirmationModal
actionText="Confirm"
onClose={close}
onAction={() => {
const suspended = !stream.suspended;
patchStream(stream.id, { suspended })
.then((res) => {
stream.suspended = suspended;
setStream({ ...stream, suspended });
})
.catch((e) => {
console.error(e);
})
.finally(close);
}}>
{!stream.suspended ? (
<div>
Are you sure you want to suspend and block this stream? Any
active stream sessions will immediately end. New sessions will
be prevented from starting until unchecked.
</div>
) : (
<div>
Are you sure you want to allow new stream sessions again?
</div>
)}
</ConfirmationModal>
)}
{terminateModal && stream && (
<ConfirmationModal
actionText="Terminate"
onClose={close}
onAction={() => {
terminateStream(stream.id)
.then((res) => {
setResultText(`sucess: ${res}`);
})
.catch((e) => {
console.error(e);
setAlertText(`${e}`);
})
.finally(close);
}}>
<div>
Are you sure you want to terminate (stop running live) stream{" "}
<b>{stream.name}</b>? Terminating a stream will break RTMP
connection.
</div>
</ConfirmationModal>
)}
<SuspendUserModal
user={streamOwner}
isOpen={suspendUserModal}
onClose={close}
onSuspend={fetchStream}
/>
{deleteModal && stream && (
<DeleteStreamModal
streamName={stream.name}
onClose={close}
onDelete={() => {
deleteStream(stream.id).then(() => Router.replace("/app/user"));
}}
/>
)}
{recordOffModal && stream && (
<Modal onClose={close}>
<h3>Are you sure you want to turn off recoding?</h3>
<p>
Future stream sessions will not be recorded. In progress stream
sessions will be recorded. Past sessions recordings will still be
available.
</p>
<Flex sx={{ justifyContent: "flex-end" }}>
<Button
type="button"
variant="outlineSmall"
onClick={close}
sx={{ mr: 2 }}>
Cancel
</Button>
<Button
type="button"
variant="secondarySmall"
onClick={() => {
close();
doSetRecord(stream, false);
}}>
Turn off recording
</Button>
</Flex>
</Modal>
)}
<Link href={backLink} passHref>
<A
sx={{
mt: 4,
fontWeight: 500,
mb: 3,
color: "text",
display: "block",
}}>
{"← stream list"}
</A>
</Link>
{stream ? (
<>
<Flex
sx={{
justifyContent: "flex-start",
alignItems: "baseline",
flexDirection: "column",
}}>
<Heading as="h3" sx={{ mb: "0.5em" }}>
{stream.name}
</Heading>
<Flex
sx={{
justifyContent: "flex-end",
mb: 3,
}}>
<Box
sx={{
display: "grid",
alignItems: "center",
gridTemplateColumns: "10em auto",
width: "60%",
fontSize: 0,
position: "relative",
}}>
<Cell>Stream name</Cell>
<Cell>{stream.name}</Cell>
<Cell>Stream ID</Cell>
<Cell>
<ClipBut text={stream.id}></ClipBut>
</Cell>
<Cell>Stream key</Cell>
<Cell>
{keyRevealed ? (
<Flex>
{stream.streamKey}
<CopyToClipboard
text={stream.streamKey}
onCopy={() => setCopied(2000)}>
<Flex
sx={{
alignItems: "center",
cursor: "pointer",
ml: 1,
}}>
<Copy
sx={{
mr: 1,
width: 14,
height: 14,
color: "offBlack",
}}
/>
{!!isCopied && (
<Box sx={{ fontSize: 12, color: "offBlack" }}>
Copied
</Box>
)}
</Flex>
</CopyToClipboard>
</Flex>
) : (
<Button
type="button"
variant="outlineSmall"
onClick={() => setKeyRevealed(true)}
sx={{ mr: 0, py: "4px", fontSize: 0 }}>
Show secret stream key
</Button>
)}
</Cell>
<Cell>RTMP ingest URL</Cell>
<Cell>
<ShowURL text="" url={globalIngestUrl} anchor={true} />
</Cell>
<Cell>Playback URL</Cell>
<Cell>
<ShowURL text="" url={globalPlaybackUrl} anchor={true} />
</Cell>
<Box
sx={{
mx: "0.4em",
mt: "0.4em",
mb: "0",
gridColumn: "1/-1",
}}>
<Box
onClick={() =>
setRegionalUrlsVisible(!regionalUrlsVisible)
}
sx={{
cursor: "pointer",
display: "inline-block",
transform: regionalUrlsVisible
? "rotate(90deg)"
: "rotate(0deg)",
transition: "transform 0.4s ease",
}}>
▶
</Box>{" "}
Regional ingest and playback URL pairs
</Box>
<Box
sx={{
gridColumn: "1/-1",
position: "relative",
overflow: "hidden",
mb: "0.8em",
}}>
<Box
sx={{
position: "relative",
overflow: "hidden",
transition: "margin-bottom .4s ease",
mb: regionalUrlsVisible ? "0" : "-100%",
display: "grid",
alignItems: "center",
gridTemplateColumns: "10em auto",
}}>
<Box
sx={{
mx: "0.4em",
mt: "0.4em",
gridColumn: "1/-1",
width: ["100%", "100%", "75%", "50%"],
}}>
The global RTMP ingest and playback URL pair above auto
detects livestreamer and viewer locations to provide the
optimal Livepeer.com experience.
<Link
href="/docs/guides/dashboard/ingest-playback-url-pair"
passHref>
<A target="_blank">
<i>
Learn more about forgoing the global ingest and
playback URLs before selecting a regional URL
pair.
</i>
</A>
</Link>
</Box>
{!ingest.length && (
<Spinner sx={{ mb: 3, width: 32, height: 32 }} />
)}
{ingest.map((_, i) => {
return (
<>
<Cell>RTMP ingest URL {i + 1}</Cell>
<Cell>
<ShowURL
text=""
url={getIngestURL(stream, false, i)}
urlToCopy={getIngestURL(stream, false, i)}
anchor={false}
/>
</Cell>
<Box
sx={{
m: "0.4em",
mb: "1.4em",
}}>
Playback URL {i + 1}
</Box>
<Box
sx={{
m: "0.4em",
mb: "1.4em",
}}>
<ShowURL
text=""
url={getPlaybackURL(stream, i)}
anchor={true}
/>
</Box>
</>
);
})}
</Box>
</Box>
<Box sx={{ m: "0.4em", gridColumn: "1/-1" }}>
<hr />
</Box>
<Cell>Record sessions</Cell>
<Box
sx={{
m: "0.4em",
justifySelf: "flex-start",
cursor: "pointer",
}}>
<Flex
sx={{
alignItems: "flex-start",
justifyItems: "center",
}}>
<Label
onClick={() => {
if (!stream.record) {
doSetRecord(stream, true);
}
}}>
<Radio
autocomplete="off"
name="record-mode"
value={`${!!stream.record}`}
checked={!!stream.record}
/>
<Flex sx={{ alignItems: "center" }}>On</Flex>
</Label>
<Label sx={{ ml: "0.5em" }}>
<Radio
autocomplete="off"
name="record-mode"
value={`${!stream.record}`}
checked={!stream.record}
onClick={(e) => {
if (stream.record) {
setRecordOffModal(true);
}
}}
/>
<Flex sx={{ alignItems: "center" }}>Off</Flex>
</Label>
<Flex
sx={{
ml: "0.5em",
minWidth: "24px",
height: "24px",
alignItems: "center",
}}>
<Help
data-tip
data-for={`tooltip-record-${stream.id}`}
sx={{
color: "muted",
cursor: "pointer",
ml: 1,
width: "18px",
height: "18px",
}}
/>
</Flex>
</Flex>
<ReactTooltip
id={`tooltip-record-${stream.id}`}
className="tooltip"
place="top"
type="dark"
effect="solid">
<p>
When checked, transcoded streaming sessions will be
recorded and stored by Livepeer.com.
<br /> Each recorded session will have a recording .m3u8
URL for playback and an MP4 download link.
<br />
This feature is currently free.
</p>
</ReactTooltip>
</Box>
<Box sx={{ m: "0.4em", gridColumn: "1/-1" }}>
<hr />
</Box>
<Cell>Suspend and block</Cell>
<Box
sx={{
m: "0.4em",
justifySelf: "flex-start",
cursor: "pointer",
}}>
<Flex
sx={{
alignItems: "flex-start",
justifyItems: "center",
}}>
<Label
onClick={() => {
if (!stream.suspended) {
setSuspendModal(true);
}
}}>
<Radio
autocomplete="off"
name="suspend-mode"
value={`${!!stream.suspended}`}
checked={!!stream.suspended}
/>
<Flex sx={{ alignItems: "center" }}>On</Flex>
</Label>
<Label sx={{ ml: "0.5em" }}>
<Radio
autocomplete="off"
name="suspend-mode"
value={`${!stream.suspended}`}
checked={!stream.suspended}
onClick={(e) => {
if (stream.suspended) {
setSuspendModal(true);
}
}}
/>
<Flex sx={{ alignItems: "center" }}>Off</Flex>
</Label>
<Flex
sx={{
ml: "0.5em",
minWidth: "24px",
height: "24px",
alignItems: "center",
}}>
<Help
data-tip
data-for={`tooltip-suspend-${stream.id}`}
sx={{
color: "muted",
cursor: "pointer",
ml: 1,
width: "18px",
height: "18px",
}}
/>
</Flex>
</Flex>
<ReactTooltip
id={`tooltip-suspend-${stream.id}`}
className="tooltip"
place="top"
type="dark"
effect="solid">
<p>
When turned on, any active stream sessions will
immediately end.
<br />
New sessions will be prevented from starting until
turned off.
</p>
</ReactTooltip>
</Box>
<Box sx={{ m: "0.4em", gridColumn: "1/-1" }}>
<hr />
</Box>
<Cell>User</Cell>
<Cell>{userField}</Cell>
<Cell>Renditions</Cell>
<Cell>
<RenditionsDetails stream={stream} />
</Cell>
<Cell>Created at</Cell>
<Cell>
<RelativeTime
id="cat"
prefix="createdat"
tm={stream.createdAt}
swap={true}
/>
</Cell>
<Cell>Last seen</Cell>
<Cell>
<RelativeTime
id="last"
prefix="lastSeen"
tm={stream.lastSeen}
swap={true}
/>
</Cell>
<Cell>Status</Cell>
<Cell>{stream.isActive ? "Active" : "Idle"}</Cell>
<Cell>Suspended</Cell>
<Cell>{stream.suspended ? "Yes" : " No"}</Cell>
{user.admin || isStaging() || isDevelopment() ? (
<>
<Cell> </Cell>
<Cell>
<strong>Admin or staging only fields:</strong>
</Cell>
</>
) : null}
{user.admin ? (
<>
<Cell> </Cell>
<Cell>
<strong>Admin only fields:</strong>
</Cell>
<Cell>Deleted</Cell>
<Cell>
{stream.deleted ? <strong>Yes</strong> : "No"}
</Cell>
<Cell>Source segments</Cell>
<Cell>{stream.sourceSegments || 0}</Cell>
<Cell>Transcoded segments</Cell>
<Cell>{stream.transcodedSegments || 0}</Cell>
<Cell>Source duration</Cell>
<Cell>
{formatNumber(stream.sourceSegmentsDuration || 0, 0)}{" "}
sec (
{formatNumber(
(stream.sourceSegmentsDuration || 0) / 60,
2
)}{" "}
min)
</Cell>
<Cell>Transcoded duration</Cell>
<Cell>
{formatNumber(
stream.transcodedSegmentsDuration || 0,
0
)}{" "}
sec (
{formatNumber(
(stream.transcodedSegmentsDuration || 0) / 60,
2
)}{" "}
min)
</Cell>
<Cell>Source bytes</Cell>
<Cell>{formatNumber(stream.sourceBytes || 0, 0)}</Cell>
<Cell>Transcoded bytes</Cell>
<Cell>
{formatNumber(stream.transcodedBytes || 0, 0)}
</Cell>
<Cell>Ingest rate</Cell>
<Cell>
{formatNumber(stream.ingestRate || 0, 3)} bytes/sec (
{formatNumber((stream.ingestRate || 0) * 8, 0)})
bits/sec
</Cell>
<Cell>Outgoing rate</Cell>
<Cell>
{formatNumber(stream.outgoingRate || 0, 3)} bytes/sec (
{formatNumber((stream.outgoingRate || 0) * 8, 0)})
bits/sec
</Cell>
<Cell>Papertrail to stream key</Cell>
<Cell>
<Box
as="a"
target="_blank"
href={`https://papertrailapp.com/groups/16613582/events?q=${stream.streamKey}`}
sx={{ userSelect: "all" }}>
{stream.streamKey}
</Box>
</Cell>
<Cell>Papertrail to playback id</Cell>
<Cell>
<Box
as="a"
target="_blank"
href={`https://papertrailapp.com/groups/16613582/events?q=${stream.playbackId}`}
sx={{ userSelect: "all" }}>
{stream.playbackId}
</Box>
</Cell>
<Cell>Papertrail to stream id</Cell>
<Cell>
<Box
as="a"
target="_blank"
href={`https://papertrailapp.com/groups/16613582/events?q=${stream.id}`}
sx={{ userSelect: "all" }}>
{stream.id}
</Box>
</Cell>
<Cell>Region/Broadcaster</Cell>
<Cell>
{region}{" "}
{broadcasterHost ? " / " + broadcasterHost : ""}
{stream && stream.mistHost
? " / " + stream.mistHost
: ""}
</Cell>
{broadcasterPlaybackUrl ? (
<>
<Cell>Broadcaster playback</Cell>
<Cell>
<Box
as="a"
target="_blank"
href={broadcasterPlaybackUrl}
sx={{ userSelect: "all" }}>
{broadcasterPlaybackUrl}
</Box>
</Cell>
</>
) : null}
</>
) : null}
</Box>
<Box
sx={{
display: "block",
alignItems: "center",
width: "40%",
}}>
<iframe
src={playerUrl}
style={{ width: "100%", aspectRatio: "4 / 3" }}
frameBorder="0"
allowFullScreen
allow="autoplay; encrypted-media; picture-in-picture"
sandbox="allow-scripts"></iframe>
</Box>
</Flex>
<TimedAlert
text={resultText}
close={() => setResultText("")}
variant="info"
/>
<TimedAlert
text={alertText}
close={() => setAlertText("")}
variant="attention"
/>
</Flex>
<Flex
sx={{
justifyContent: "flex-end",
mb: 3,
}}>
{userIsAdmin ? (
<Flex>
<Button
sx={{ mr: 3 }}
type="button"
variant="outlineSmall"
onClick={() => setTerminateModal(true)}>
Terminate
</Button>
</Flex>
) : null}
{userIsAdmin ? (
<Flex>
<Button
sx={{ mr: 3 }}
type="button"
variant="outlineSmall"
disabled={
streamOwner?.id === user?.id ||
streamOwner?.suspended === true
}
onClick={() => setSuspendUserModal(true)}>
Suspend User
</Button>
</Flex>
) : null}
<Button
type="button"
variant="outlineSmall"
onClick={() => setDeleteModal(true)}>
Delete
</Button>
</Flex>
<StreamSessionsTable
streamId={stream.id}
streamName={stream.name}
/>
</>
) : notFound ? (
<Box>Not found</Box>
) : (
<Flex sx={{ justifyContent: "center", alignItems: "center" }}>
<Spinner sx={{ mr: "1em" }} />
<Box sx={{ color: "text" }}>Loading</Box>
</Flex>
)}
</Container>
</TabbedLayout>
);
}
Example #19
Source File: PayloadManager.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
PayloadManagerPage: React.FC<PayloadManagerPageProp> = (props) => {
const [groups, setGroups] = useState<string[]>([])
const [selected, setSelected] = useState("")
const [response, setResponse] = useState<QueryGeneralResponse<Payload>>()
const [params, setParams] = useState<QueryPayloadParams>({
Keyword: "",
Group: "",
Pagination: {Page: 1, Limit: 20, Order: "desc", OrderBy: "updated_at"}
})
const [selectedRows, setSelectedRows] = useState<Payload[]>([])
const [loading, setLoading] = useState(false)
const rowSelection = {
selectedRowKeys: selectedRows.map((item) => item.Id),
onChange: (selectedRowKeys, selectedRows) => setSelectedRows(selectedRows),
fixed: true
}
const pagination: PaginationSchema | undefined = response?.Pagination
const updateGroup = () => {
ipcRenderer
.invoke("GetAllPayloadGroup")
.then((data: { Groups: string[] }) => {
setGroups(data.Groups || [])
})
.catch((e: any) => {
failed(e?.details || "call GetAllPayloadGroup failed")
})
.finally()
}
const updateDict = (page?: number, limit?: number) => {
ipcRenderer
.invoke("QueryPayload", {
...params,
Group: selected,
Pagination: {
...params.Pagination,
Page: page || params.Pagination.Page,
Limit: limit || params.Pagination.Limit
}
} as QueryPayloadParams)
.then((data) => {
setResponse(data)
})
.catch((e: any) => {
failed(e?.details || "query payload failed")
})
}
const delDictContent = (id?: number) => {
let params: any = {}
if (id !== undefined) params.Id = +id
else params.Ids = selectedRows.map((item) => +item.Id)
ipcRenderer
.invoke("DeletePayloadByContent", params)
.then(() => {
setSelectedRows([])
updateDict()
})
.catch((e: any) => {
failed("batch delete failed")
})
}
useEffect(() => {
updateGroup()
}, [])
useEffect(() => {
if (!selected) {
return
}
updateDict()
}, [selected])
return (
<div className='payload-manager-page'>
<PageHeader
title={"Payload / 字典管理"}
subTitle={`增加 / 删除 / 管理字典,可以通过 fuzz 模块 {{x(字典名)}} 来渲染`}
/>
<Row gutter={18} style={{flexGrow: 1}}>
<Col span={8}>
<AutoCard
title={"选择 / 查看已有字典"}
size={"small"} loading={loading}
bordered={false}
bodyStyle={{overflow: "auto"}}
extra={
!props.readOnly && <Form size={"small"} onSubmitCapture={(e) => e.preventDefault()}>
<Form.Item style={{marginBottom: 0}} label={" "} colon={false}>
<Button.Group>
<Button
size={"small"}
onClick={() => {
let m = showModal({
title: "创建新的 Payload 组/字典",
content: (
<>
<CreatePayloadGroup
onLoading={() => {
setLoading(true)
}}
onLoadingFinished={() => {
setTimeout(() => setLoading(false), 300)
}}
Group={""}
onFinished={(e) => {
info("创建/修改 Payload 字典/组成功")
updateGroup()
m.destroy()
}}
/>
</>
),
width: "60%"
})
}}
>
新增 / 扩充字典
</Button>
<Button
size={"small"}
onClick={() => {
let m = showModal({
title: "上传新的 Payload 组/字典",
content: (
<>
<UploadPayloadGroup
Group={""}
onFinished={(e) => {
info("上传 Payload 字典/组成功")
updateGroup()
m.destroy()
}}
/>
</>
),
width: "60%",
maskClosable: false
})
}}
>
上传字典
</Button>
</Button.Group>
</Form.Item>
</Form>
}
>
<List<string>
style={{height: 200}}
dataSource={groups}
renderItem={(element, index) => {
return (
<List.Item id={index.toString()}>
<Button.Group style={{width: "100%", textAlign: "left"}}>
<Button
style={{width: "100%", textAlign: "left"}}
type={selected === element ? "primary" : undefined}
onClick={(e) => setSelected(element)}
>
字典分组名:{element}
</Button>
{props.selectorHandle && <Popconfirm title={"确定要使用该字典?"}
onConfirm={() => {
props.selectorHandle && props.selectorHandle(fuzzTag(element))
}}
>
<Button type={"primary"} icon={<ThunderboltFilled/>}/>
</Popconfirm>}
{!props.readOnly && <Popconfirm
title={"确定删除该字典吗?"}
onConfirm={(e) => {
ipcRenderer
.invoke("DeletePayloadByGroup", {
Group: element
})
.then(() => {
updateGroup()
if (selected === element) {
setSelected("")
setResponse(undefined)
}
})
.catch((e: any) => {
failed("Delete Payload By Group failed")
})
}}
>
<Button
danger={true}
icon={<DeleteOutlined/>}
type={selected === element ? "primary" : undefined}
/>
</Popconfirm>}
</Button.Group>
</List.Item>
)
}}
/>
</AutoCard>
</Col>
<Col span={16}>
<AutoCard
title={
<>
<span>字典内容</span>
{selectedRows.length > 0 && !props.readOnly && (
<Button size='small' type='link' danger onClick={() => delDictContent()}>
批量删除
</Button>
)}
</>
}
size={"small"}
bordered={false}
bodyStyle={{overflow: "auto", padding: 0}}
extra={
props.readOnly ?
(
!!props.selectorHandle ? <Button size={"small"} type={"primary"} onClick={() => {
props.selectorHandle && props.selectorHandle(`{{x(${selected})}}`)
}}>
选择该Fuzz标签
</Button> : <CopyToClipboard
text={`{{x(${selected})}}`}
onCopy={(text, ok) => {
if (ok) success("已复制到粘贴板")
}}
>
<Button size={"small"}>复制Fuzz标签</Button>
</CopyToClipboard>
) : <Form
size={"small"}
onSubmitCapture={(e) => {
e.preventDefault()
updateDict(1, 20)
}}
layout={"inline"}
style={{marginBottom: 0}}
>
<Form.Item style={{marginBottom: 0}}>
{selected && <Tag color={"geekblue"}>{selected}</Tag>}
</Form.Item>
<InputItem
label={"搜索"}
style={{marginBottom: 0}}
setValue={(Keyword) => setParams({...params, Keyword})}
value={params.Keyword}
/>
<Form.Item colon={false} label={" "} style={{marginBottom: 0}}>
<Button.Group>
<Button type='primary' htmlType='submit'>
{" "}
Search{" "}
</Button>
{!!props.selectorHandle ? <Button type={"primary"} onClick={() => {
props.selectorHandle && props.selectorHandle(`{{x(${selected})}}`)
}}>
选择该Fuzz标签
</Button> : <CopyToClipboard
text={`{{x(${selected})}}`}
onCopy={(text, ok) => {
if (ok) success("已复制到粘贴板")
}}
>
<Button>复制Fuzz标签</Button>
</CopyToClipboard>}
</Button.Group>
</Form.Item>
</Form>
}
>
<Table<Payload>
style={{height: 200}}
bordered={true}
size={"small"}
rowKey={(row) => row.Id}
rowSelection={rowSelection}
columns={[
{title: "所属字典", render: (e: Payload) => <Tag>{e.Group}</Tag>},
{
title: "字典内容",
render: (e: Payload) => (
<Text style={{width: 500}} ellipsis={{tooltip: true}}>
{e.Content}
</Text>
)
},
{
title: "操作",
fixed: "right",
render: (e: Payload) => (
<Button danger onClick={() => delDictContent(e.Id)}>
删除
</Button>
)
}
]}
onChange={(p) => {
updateDict(p.current, p.pageSize)
}}
pagination={{
size: "small",
pageSize: pagination?.Limit || 10,
total: response?.Total || 0,
showTotal: (i) => <Tag>共{i}条历史记录</Tag>
}}
dataSource={response?.Data}
/>
</AutoCard>
</Col>
</Row>
</div>
)
}
Example #20
Source File: index.tsx From livepeer-com with MIT License | 4 votes |
TokenTable = ({
title = "API Keys",
userId,
}: {
title?: string;
userId: string;
}) => {
const { getApiTokens, deleteApiToken } = useApi();
const tableProps = useTableState({ tableId: "tokenTable" });
const deleteDialogState = useToggleState();
const createDialogState = useToggleState();
const [savingDeleteDialog, setSavingDeleteDialog] = useState(false);
const [openSnackbar] = useSnackbar();
const columns = useMemo(
() => [
{
Header: "Name",
accessor: "name",
Cell: TextCell,
sortType: (...params: SortTypeArgs) =>
stringSort("original.name.children", ...params),
},
{
Header: "Token",
accessor: "token",
width: 400,
disableSortBy: true,
Cell: TextCell,
},
{
Header: "Last Used",
accessor: "lastUsed",
Cell: DateCell,
sortType: (...params: SortTypeArgs) =>
dateSort("original.lastUsed.date", ...params),
},
{
Header: "Created",
accessor: "createdAt",
Cell: DateCell,
sortType: (...params: SortTypeArgs) =>
dateSort("original.createdAt.date", ...params),
},
{
Header: "CORS Access",
accessor: "cors",
Cell: TextCell,
disableSortBy: true,
},
],
[]
);
const Key = ({ token }) => {
const [keyRevealed, setKeyRevealed] = useState(false);
return (
<Box>
{keyRevealed ? (
<HoverCardRoot openDelay={200}>
<HoverCardTrigger>
<Flex css={{ height: 25, ai: "center" }}>
<CopyToClipboard
text={token.id}
onCopy={() => openSnackbar("Copied to clipboard")}>
<Box
css={{
fontFamily: "monospace",
cursor: "pointer",
fontSize: "$1",
}}>
{token.id}
</Box>
</CopyToClipboard>
</Flex>
</HoverCardTrigger>
<HoverCardContent>
<Text
variant="gray"
css={{
backgroundColor: "$panel",
borderRadius: 6,
px: "$3",
py: "$1",
fontSize: "$1",
display: "flex",
ai: "center",
}}>
<CopyIcon /> <Box css={{ ml: "$2" }}>Click to copy</Box>
</Text>
</HoverCardContent>
</HoverCardRoot>
) : (
<Button size="1" type="button" onClick={() => setKeyRevealed(true)}>
Reveal key
</Button>
)}
</Box>
);
};
const CorsCell = (params: { cors: ApiToken["access"]["cors"] }) => {
const { cors } = params;
if (!cors?.allowedOrigins?.length) {
return (
<Tooltip
content="This is the most secure mode for API keys, blocking access from any webpage."
multiline>
<Label>None</Label>
</Tooltip>
);
}
const accessLevel = cors.fullAccess ? "Full" : "Restricted";
return (
<Tooltip
content={
cors.allowedOrigins.includes("*")
? `${accessLevel} access allowed from any origin`
: `${accessLevel} access allowed from: ${cors.allowedOrigins.join(
", "
)}`
}
multiline>
<Label>
<i>{accessLevel}</i>
</Label>
</Tooltip>
);
};
const fetcher: Fetcher<TokenTableData> = useCallback(async () => {
const [tokens, nextCursor, resp, count] = await getApiTokens(userId, {
count: true,
});
if (!resp.ok || !Array.isArray(tokens)) {
throw resp;
}
return {
nextCursor,
count,
rows: tokens.map((token) => {
return {
id: token.id,
token: {
children: <Key token={token} />,
},
name: {
children: token.name,
},
createdAt: {
date: new Date(token.createdAt),
fallback: <Box css={{ color: "$primary8" }}>—</Box>,
},
lastUsed: {
date: new Date(token.lastSeen),
fallback: <i>unused</i>,
},
cors: {
children: <CorsCell cors={token.access?.cors} />,
},
};
}),
};
}, [userId]);
const emptyState = (
<Flex
direction="column"
justify="center"
css={{
margin: "0 auto",
height: "calc(100vh - 400px)",
maxWidth: 450,
}}>
<Heading css={{ fontWeight: 500, mb: "$3" }}>Create an API key</Heading>
<Text variant="gray" css={{ lineHeight: 1.5, mb: "$3" }}>
API keys allow you to authenticate API requests in your app
</Text>
<Link href="/docs/guides/api" passHref>
<A variant="primary" css={{ display: "flex", ai: "center", mb: "$5" }}>
<Box>Learn more</Box>
<ArrowRightIcon />
</A>
</Link>
<Button
onClick={() => createDialogState.onOn()}
css={{ alignSelf: "flex-start" }}
size="2"
variant="primary">
<PlusIcon />{" "}
<Box as="span" css={{ ml: "$2" }}>
Create API key
</Box>
</Button>
</Flex>
);
return (
<>
<Table
{...tableProps}
header={
<>
<Heading size="2" css={{ fontWeight: 600 }}>
{title}
</Heading>
</>
}
columns={columns}
fetcher={fetcher}
rowSelection="all"
emptyState={emptyState}
selectAction={{
onClick: deleteDialogState.onOn,
children: (
<>
<Cross1Icon /> <Box css={{ ml: "$2" }}>Delete</Box>
</>
),
css: { display: "flex", alignItems: "center" },
// @ts-ignore
size: "2",
}}
createAction={{
onClick: createDialogState.onOn,
css: { display: "flex", alignItems: "center" },
children: (
<>
<PlusIcon />{" "}
<Box as="span" css={{ ml: "$2" }}>
Create key
</Box>
</>
),
}}
/>
{/* Delete dialog */}
<AlertDialog
open={deleteDialogState.on}
onOpenChange={deleteDialogState.onOff}>
<AlertDialogContent
css={{ maxWidth: 450, px: "$5", pt: "$4", pb: "$4" }}>
<AlertDialogTitle asChild>
<Heading size="1">
Delete {tableProps.state.selectedRows.length} API token
{tableProps.state.selectedRows.length > 1 && "s"}?
</Heading>
</AlertDialogTitle>
<AlertDialogDescription asChild>
<Text
size="3"
variant="gray"
css={{ mt: "$2", lineHeight: "22px" }}>
This will permanently remove the API token
{tableProps.state.selectedRows.length > 1 && "s"}. This action
cannot be undone.
</Text>
</AlertDialogDescription>
<Flex css={{ jc: "flex-end", gap: "$3", mt: "$5" }}>
<AlertDialogCancel asChild>
<Button size="2" onClick={deleteDialogState.onOff} ghost>
Cancel
</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button
size="2"
disabled={savingDeleteDialog}
onClick={async (e) => {
try {
e.preventDefault();
setSavingDeleteDialog(true);
const promises = tableProps.state.selectedRows.map(
async (row) => {
return deleteApiToken(row.original.id as string);
}
);
await Promise.all(promises);
await tableProps.state.invalidate();
openSnackbar(
`${tableProps.state.selectedRows.length} stream${
tableProps.state.selectedRows.length > 1 ? "s" : ""
} deleted.`
);
deleteDialogState.onOff();
} finally {
setSavingDeleteDialog(false);
}
}}
variant="red">
{savingDeleteDialog && (
<Spinner
css={{
color: "$hiContrast",
width: 16,
height: 16,
mr: "$2",
}}
/>
)}
Delete
</Button>
</AlertDialogAction>
</Flex>
</AlertDialogContent>
</AlertDialog>
{/* Create dialog */}
<CreateTokenDialog
isOpen={createDialogState.on}
onClose={createDialogState.onOff}
onOpenChange={createDialogState.onToggle}
onCreateSuccess={tableProps.state.invalidate}
/>
</>
);
}
Example #21
Source File: index.tsx From livepeer-com with MIT License | 4 votes |
StreamSessionsTable = ({
title = "Sessions",
streamId,
emptyState = defaultEmptyState,
border = false,
tableLayout = "fixed",
}: {
title?: string;
streamId: string;
emptyState?: React.ReactNode;
border?: boolean;
tableLayout?: string;
}) => {
const { user, getStreamSessions } = useApi();
const tableProps = useTableState({
tableId: "streamSessionsTable",
});
const [openSnackbar] = useSnackbar();
const columns = useMemo(
() => [
{
Header: "Created at",
accessor: "createdAt",
Cell: DateCell,
sortType: (...params: SortTypeArgs) =>
dateSort("original.createdAt.date", ...params),
},
{
Header: "Duration",
accessor: "sourceSegmentsDuration",
Cell: DurationCell,
sortType: (...params: SortTypeArgs) =>
numberSort(
"original.sourceSegmentsDuration.sourceSegmentsDuration",
...params
),
},
{
Header: "Recording URL",
accessor: "recordingUrl",
Cell: RecordingUrlCell,
disableSortBy: true,
},
],
[]
);
const fetcher: Fetcher<SessionsTableData> = useCallback(
async (state) => {
const [streams, nextCursor, count] = await getStreamSessions(
streamId,
state.cursor,
state.pageSize,
formatFiltersForApiRequest(state.filters, {
parseNumber: (n) => n * 60,
}),
true
);
return {
nextCursor,
count,
rows: streams.map((stream: any) => {
return {
id: stream.id,
recordingUrl: {
id: stream.id,
showMP4: true,
profiles:
stream.recordingUrl &&
stream.recordingStatus === "ready" &&
stream.profiles?.length
? [{ name: "source" }, ...stream.profiles]
: undefined,
children:
stream.recordingUrl && stream.recordingStatus === "ready" ? (
<HoverCardRoot openDelay={200}>
<HoverCardTrigger>
<Flex css={{ height: 25, ai: "center" }}>
<CopyToClipboard
text={stream.recordingUrl}
onCopy={() => openSnackbar("Copied to clipboard")}>
<Flex
css={{
cursor: "pointer",
fontSize: "$1",
ai: "center",
}}>
<Box css={{ mr: "$1" }}>
{truncate(stream.recordingUrl, 24)}
</Box>
<CopyIcon />
</Flex>
</CopyToClipboard>
</Flex>
</HoverCardTrigger>
<HoverCardContent>
<Text
variant="gray"
css={{
backgroundColor: "$panel",
borderRadius: 6,
px: "$3",
py: "$1",
fontSize: "$1",
display: "flex",
ai: "center",
}}>
<Box css={{ ml: "$2" }}>{stream.recordingUrl}</Box>
</Text>
</HoverCardContent>
</HoverCardRoot>
) : (
<Box css={{ color: "$primary8" }}>—</Box>
),
mp4Url: stream.recordingUrl ? stream.recordingUrl : undefined,
},
sourceSegmentsDuration: {
sourceSegmentsDuration: stream.sourceSegmentsDuration || 0,
status: stream.recordingStatus,
},
createdAt: {
date: new Date(stream.createdAt),
fallback: <Box css={{ color: "$primary8" }}>—</Box>,
},
};
}),
};
},
[getStreamSessions, user.id]
);
return (
<Box>
<Table
{...tableProps}
header={
<>
<Heading>{title}</Heading>
</>
}
border={border}
filterItems={filterItems}
columns={columns}
fetcher={fetcher}
rowSelection={null}
initialSortBy={[{ id: "createdAt", desc: true }]}
showOverflow={true}
emptyState={emptyState}
tableLayout={tableLayout}
/>
</Box>
);
}
Example #22
Source File: index.tsx From livepeer-com with MIT License | 4 votes |
AllSessionsTable = ({ title = "Sessions" }: { title?: string }) => {
const { user, getStreamSessionsByUserId } = useApi();
const tableProps = useTableState({
tableId: "allSessionsTable",
});
const [openSnackbar] = useSnackbar();
const columns = useMemo(
() => [
{
Header: "Stream",
accessor: "parentStream",
Cell: TextCell,
sortType: (...params: SortTypeArgs) =>
stringSort("original.parentStream.name", ...params),
},
{
Header: "Created at",
accessor: "createdAt",
Cell: DateCell,
sortType: (...params: SortTypeArgs) =>
dateSort("original.createdAt.date", ...params),
},
{
Header: "Duration",
accessor: "sourceSegmentsDuration",
Cell: DurationCell,
sortType: (...params: SortTypeArgs) =>
numberSort(
"original.sourceSegmentsDuration.sourceSegmentsDuration",
...params
),
},
{
Header: "Recording URL",
accessor: "recordingUrl",
Cell: RecordingUrlCell,
disableSortBy: true,
},
],
[]
);
const fetcher: Fetcher<SessionsTableData> = useCallback(
async (state) => {
const [streams, nextCursor, count] = await getStreamSessionsByUserId(
user.id,
state.cursor,
state.pageSize,
state.order,
formatFiltersForApiRequest(state.filters, {
parseNumber: (n) => n * 60,
}),
true
);
return {
nextCursor,
count,
rows: streams.map((stream: any) => {
return {
id: stream.id,
parentStream: {
id: stream.parentId,
name: stream.parentStream.name,
children: (
<A variant="primary" as={Box}>
{stream.parentStream.name}
</A>
),
tooltipChildren: stream.createdByTokenName ? (
<>
Created by stream <b>{stream.parentStream.name}</b>
</>
) : null,
href: `/dashboard/streams/${stream.parentId}`,
},
recordingUrl: {
id: stream.id,
showMP4: true,
profiles:
stream.recordingUrl &&
stream.recordingStatus === "ready" &&
stream.profiles?.length
? [{ name: "source" }, ...stream.profiles]
: undefined,
children:
stream.recordingUrl && stream.recordingStatus === "ready" ? (
<HoverCardRoot openDelay={200}>
<HoverCardTrigger>
<Flex css={{ ai: "center" }}>
<CopyToClipboard
text={stream.recordingUrl}
onCopy={() => openSnackbar("Copied to clipboard")}>
<Flex
css={{
cursor: "pointer",
fontSize: "$1",
ai: "center",
}}>
<Box css={{ mr: "$1" }}>
{truncate(stream.recordingUrl, 24)}
</Box>
<CopyIcon />
</Flex>
</CopyToClipboard>
</Flex>
</HoverCardTrigger>
<HoverCardContent>
<Text
variant="gray"
css={{
backgroundColor: "$panel",
borderRadius: 6,
px: "$3",
py: "$1",
fontSize: "$1",
display: "flex",
ai: "center",
}}>
<Box css={{ ml: "$2" }}>{stream.recordingUrl}</Box>
</Text>
</HoverCardContent>
</HoverCardRoot>
) : (
<Box css={{ color: "$primary8" }}>—</Box>
),
mp4Url: stream.recordingUrl ? stream.recordingUrl : undefined,
},
sourceSegmentsDuration: {
sourceSegmentsDuration: stream.sourceSegmentsDuration || 0,
status: stream.recordingStatus,
},
createdAt: {
date: new Date(stream.createdAt),
fallback: <i>unseen</i>,
},
};
}),
};
},
[getStreamSessionsByUserId, user.id]
);
const emptyState = (
<Flex
direction="column"
justify="center"
css={{
margin: "0 auto",
height: "calc(100vh - 400px)",
maxWidth: 450,
}}>
<Heading css={{ fontWeight: 500, mb: "$3" }}>No sessions</Heading>
<Text variant="gray" css={{ lineHeight: 1.5, mb: "$3" }}>
Stream sessions belong to parent streams.
</Text>
<Link href="/docs/api-reference/session/overview" passHref>
<A variant="primary" css={{ display: "flex", ai: "center", mb: "$5" }}>
<Box>Learn more</Box>
<ArrowRightIcon />
</A>
</Link>
</Flex>
);
return (
<>
<Table
{...tableProps}
columns={columns}
fetcher={fetcher}
initialSortBy={[{ id: "createdAt", desc: true }]}
showOverflow={true}
filterItems={filterItems}
emptyState={emptyState}
header={
<>
<Heading size="2" css={{ fontWeight: 600 }}>
{title}
</Heading>
</>
}
/>
</>
);
}
Example #23
Source File: index.tsx From react-in-out-textarea with MIT License | 4 votes |
InOutTextarea: FC<Props> = props => {
const [menuInOptions, setMenuInOptions] = useState<InOptions>([]);
const [menuOutOptions, setMenuOutOptions] = useState<OutOptions>([]);
const [inOptionsMenuRef, inOptionsMenuRefSizes] = useDimensions({
liveMeasure,
});
const [outOptionsMenuRef, outOptionsMenuRefSizes] = useDimensions({
liveMeasure,
});
const [convertCardRef, convertCardSizes] = useDimensions({ liveMeasure });
const [showAdditionalInOptions, setShowAdditionalInOptions] = useState<
boolean
>(false);
const [showAdditionalOutOptions, setShowAdditionalOutOptions] = useState<
boolean
>(false);
const onInMoreOptionsClick = useCallback(() => {
setShowAdditionalOutOptions(false);
setShowAdditionalInOptions(!showAdditionalInOptions);
}, [showAdditionalInOptions]);
const onOutMoreOptionsClick = useCallback(() => {
setShowAdditionalInOptions(false);
setShowAdditionalOutOptions(!showAdditionalOutOptions);
}, [showAdditionalOutOptions]);
const {
inOptions,
inValue,
onInInput,
onInOptionsUpdate,
outOptions,
onOutOptionsUpdate,
outValue,
maxContentLength,
onCopy,
maxContentLengthIndicator,
autoCloseMenuOnOptionSelection = true,
} = props;
const onInOverlayOptionClick = useCallback(() => {
if (autoCloseMenuOnOptionSelection) setShowAdditionalInOptions(false);
}, [autoCloseMenuOnOptionSelection]);
const onOutOverlayOptionClick = useCallback(() => {
if (autoCloseMenuOnOptionSelection) setShowAdditionalOutOptions(false);
}, [autoCloseMenuOnOptionSelection]);
return (
<ConvertCard>
<CaseBar>
<SideBar>
<OptionsContainer>
{inOptions
.sort(a => {
if (a.active) return -1;
return 0;
})
.map(option => {
return (
<InMenuOptionStuff
key={option.name}
inOptionsMenuRefSizes={inOptionsMenuRefSizes}
liveMeasure={liveMeasure}
menuOptions={menuInOptions}
option={option}
inOptions={inOptions}
onInOptionsUpdate={onInOptionsUpdate}
setMenuOptions={setMenuInOptions}
/>
);
})}
</OptionsContainer>
<MoreOptionsIconContainer
ref={inOptionsMenuRef}
onClick={onInMoreOptionsClick}
active={showAdditionalInOptions}
/>
</SideBar>
<Spacer />
<SideBar>
<OptionsContainer>
{outOptions
.sort(a => {
if (a.activeClicked) return -1;
if (a.active) return -1;
return 0;
})
.map(option => {
return (
<OutMenuOptionStuff
key={option.name}
outOptionsMenuRefSizes={outOptionsMenuRefSizes}
liveMeasure={liveMeasure}
menuOptions={menuOutOptions}
option={option}
outOptions={outOptions}
onOutOptionsUpdate={onOutOptionsUpdate}
setMenuOptions={setMenuOutOptions}
/>
);
})}
</OptionsContainer>
<MoreOptionsIconContainer
right
ref={outOptionsMenuRef}
onClick={onOutMoreOptionsClick}
active={showAdditionalOutOptions}
/>
</SideBar>
</CaseBar>
<ConvertCardContent ref={convertCardRef}>
{showAdditionalOutOptions && (
<OptionsOverlay
convertCardSizes={convertCardSizes}
shownMenuOptions={menuOutOptions}
allMenuOptions={outOptions}
onAllMenuOptionsUpdate={onOutOptionsUpdate}
onOptionClick={onOutOverlayOptionClick}
/>
)}
{showAdditionalInOptions && (
<OptionsOverlay
convertCardSizes={convertCardSizes}
shownMenuOptions={menuInOptions}
allMenuOptions={inOptions}
onAllMenuOptionsUpdate={onInOptionsUpdate}
onOptionClick={onInOverlayOptionClick}
/>
)}
<TextAreaContentTop>
<Flex>
<TextAreaWrapper>
<Textarea
data-test="from-textarea"
placeholder="..."
rows={2}
smallerFont={false}
value={inValue}
maxLength={maxContentLength}
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => {
if (
event.target.value === null ||
event.target.value === undefined
)
return;
onInInput(event.target.value);
}}
/>
</TextAreaWrapper>
<IconContainer onClick={() => onInInput('')}>
<IconX size={32} />
</IconContainer>
</Flex>
{maxContentLengthIndicator &&
maxContentLengthIndicator.show &&
maxContentLength && (
<MaxContentLengthIndicator
currentLength={inValue ? inValue.length : 0}
maxContentLength={maxContentLength}
maxContentLengthIndicator={maxContentLengthIndicator}
/>
)}
</TextAreaContentTop>
<TextAreaContentBottom>
<TextAreaWrapper>
<Textarea
disabled
smallerFont={false}
showCopyCursor
value={outValue}
/>
</TextAreaWrapper>
<CopyToClipboard
text={outValue}
onCopy={() => {
if (onCopy) {
onCopy();
}
}}
>
<IconContainer>
<IconCopy size={24} />
</IconContainer>
</CopyToClipboard>
</TextAreaContentBottom>
</ConvertCardContent>
</ConvertCard>
);
}
Example #24
Source File: Share.tsx From gateway-ui with BSD 3-Clause "New" or "Revised" License | 4 votes |
SharePage = ({ uploadReference, metadata }: Props): ReactElement => {
const classes = useStyles()
const navigate = useNavigate()
const isWebsite = metadata?.isWebsite
const bzzLink = `https://${encodeManifestReference(uploadReference)}.${BZZ_LINK_DOMAIN}/`
const linkHeader = isWebsite ? 'Bzz Link' : 'Web link'
const linkUrl = isWebsite ? bzzLink : `${GATEWAY_URL}${ROUTES.ACCESS_HASH(uploadReference)}`
const [copiedToClipboard, setCopiedToClipboard] = useState<boolean>(false)
const [activeValue, setActiveValue] = useState<string>(uploadReference)
return (
<Layout
top={[
<Header
key="top1"
leftAction={
<IconButton
onClick={() => {
navigate(ROUTES.LANDING_PAGE)
}}
>
<ArrowLeft strokeWidth={1} />
</IconButton>
}
>
{text.shareHashPage.header}
</Header>,
<Typography key="top2" variant="subtitle1">
{text.shareHashPage.tagline}
</Typography>,
]}
center={[
<Tabs
key="center1"
onChange={reference => {
if (reference !== activeValue) {
setActiveValue(reference)
setCopiedToClipboard(false)
}
}}
values={[
{
label: linkHeader,
component: (
<div>
<Paper
square
elevation={0}
style={{ overflowWrap: 'break-word', textAlign: 'left', padding: 16, margin: 4 }}
>
<Typography variant="caption">{linkUrl}</Typography>
</Paper>
{isWebsite && (
<Button
variant="contained"
style={{ margin: 4, width: 'auto' }}
className={classes.button}
href={bzzLink}
target="blank"
>
<ExternalLink strokeWidth={1} />
{text.accessHashPage.openWebsite}
<ExternalLink style={{ opacity: 0 }} />
</Button>
)}
</div>
),
value: linkUrl,
},
{
label: 'Swarm hash',
component: (
<Paper
square
elevation={0}
style={{ overflowWrap: 'break-word', textAlign: 'left', padding: 16, margin: 4 }}
>
<Typography variant="caption">{uploadReference}</Typography>
</Paper>
),
value: uploadReference,
},
]}
/>,
]}
bottom={[
<Typography key="bottom1" variant="body2">
{text.shareHashPage.disclaimer}
</Typography>,
<Footer key="bottom2">
<CopyToClipboard text={activeValue}>
<Button
variant="contained"
className={classes.button}
size="large"
onClick={e => {
e.stopPropagation()
setCopiedToClipboard(true)
}}
>
{copiedToClipboard ? <Check strokeWidth={1} /> : <Clipboard strokeWidth={1} />}
{copiedToClipboard ? text.shareHashPage.copyLinkActionSuccess : text.shareHashPage.copyLinkAction}
{/* Needed to properly align icon to the right and label to center */}
<Clipboard style={{ opacity: 0 }} />
</Button>
</CopyToClipboard>
</Footer>,
]}
/>
)
}
Example #25
Source File: RunningTimesModal.tsx From yugong with MIT License | 4 votes |
RunningTimesModal: React.FC<Props> = ({
visible = false,
data,
onCancel,
}) => {
const [state, setstate] = useState<AnyObjectType>({});
const [showHelp, setShowHelp] = useState<boolean>(false);
const [runningPath, setRunningPath] = useState<string>()
useEffect(() => {
setstate(data);
}, [data]);
const onChange = useCallback(
(e) => {
const filterdata = {};
Object.keys(state).forEach((key) => {
if (key.indexOf(e.target.value) !== -1) {
filterdata[key] = state[key];
}
});
if (!e.target.value) {
setstate(data);
} else {
setstate(filterdata);
}
},
[data, state]
);
const handleClipboard = useCallback(
(data) => {
console.log(data);
const arr = data.namespace;
const path = arr.slice(1, arr.length);
const runningPath = path.join('.');
setRunningPath(runningPath);
return true
},
[],
)
const onClip = useCallback(
() => {
const jspath = `js{{runningTimes.${runningPath}}}`;
const path = `{{${runningPath}}}`;
console.log(jspath, path);
},
[runningPath],
)
return (
<>
<Modal visible={visible} footer={null} onCancel={onCancel}>
<div className={s.blank}>
<Row>
<Col>
<Search onChange={onChange} placeholder="查找全局发布变量" />
</Col>
<Col>
<div className={s.help} onClick={() => setShowHelp(true)}><ExceptionOutlined /> 运行时与EventEmitter</div>
</Col>
</Row>
<ReactJson
src={state}
collapsed={1}
style={{ padding: "20px" }}
name="runningTimes"
enableClipboard={handleClipboard}
/>
{runningPath ? <Row gutter={10}>
<Col>规则路径:</Col>
<Col>{`{{${runningPath}}}`}</Col>
<Col className={s.icon}>
<CopyToClipboard
text={`{{${runningPath}}}`}
onCopy={() => message.info({ content: '已拷贝规则路径' })}
>
<span onClick={onClip}>{svgIcon()}</span>
</CopyToClipboard>
</Col></Row> : null}
{runningPath ? <Row gutter={10}>
<Col>脚本路径:</Col>
<Col>{`js{{runningTimes.${runningPath}}}`}</Col>
<Col className={s.icon}>
<CopyToClipboard
text={`js{{runningTimes.${runningPath}}}`}
onCopy={() => message.info({ content: '已拷贝脚本路径' })}
>
<span onClick={onClip}>{svgIcon()}</span>
</CopyToClipboard>
</Col>
</Row> : null}
</div>
</Modal>
<Modal
width={1000}
footer={null}
visible={showHelp}
onCancel={() => setShowHelp(false)}
>
<img src={core} alt="运行时与事件调度" />
</Modal>
</>
);
}
Example #26
Source File: CodeEditor.tsx From yugong with MIT License | 4 votes |
Codeeditor: React.FC<Props> = () => {
const activationItem = useSelector(
(state: RootState) => state.activationItem
);
const appData = useSelector((state: RootState) => state.appData);
const dispatch = useDispatch<Dispatch>();
const [jsonData, setJsonData] = useState<AppDataLayoutItemTypes>();
const [jsonMode, setJsonMode] = useState<"view" | "code">("view");
useEffect(() => {
setJsonData({ ...activationItem });
}, [activationItem]);
const [, setLocalStorage] = useLocalStorage("appData", null);
const jsoneditor = useRef<JSONEditor>();
const container = useRef<any>();
const onsubmit = useCallback(() => {
try {
var json = jsoneditor.current?.get();
if (
json &&
activationItem.moduleId === json.moduleId &&
activationItem.moduleId === json.layout?.i
) {
setJsonData(json);
dispatch.activationItem.updateActivationItem(json);
const operateData = produce([...appData].map((item) => {
if (item.moduleId === json.moduleId) {
return json;
}
return item;
}), undefined, {
name: `修改组件${activationItem.moduleName || activationItem.moduleId}`,
desc: 'code'
});
dispatch.appData.updateAppData(operateData);
setLocalStorage(operateData);
dispatch.controller.forceUpdateByStateTag();
}
} catch (e) {
return;
}
}, [activationItem.moduleId, activationItem.moduleName, appData, dispatch.activationItem, dispatch.appData, dispatch.controller, setLocalStorage]);
useEffect(() => {
if (container.current && jsonData) {
jsoneditor.current = new JSONEditor(container.current, {
mode: jsonMode,
mainMenuBar: false,
});
jsoneditor.current.set(jsonData);
}
return () => {
if (jsoneditor.current) {
jsoneditor.current.destroy();
}
};
}, [jsonData, jsonMode]);
const onChangeJsonMode = useCallback((e) => {
jsoneditor.current?.setMode(e.target.value);
setJsonMode(e.target.value);
}, []);
return (
<>
<div className={s.toolbar}>
<div className={s.modeid}>{activationItem.moduleId}</div>
<div>
<CopyToClipboard
text={JSON.stringify(activationItem)}
onCopy={() => message.info("已复制到剪切板")}
>
<Button size="small" icon={<CopyOutlined alt="复制到剪切板" />}>
复制
</Button>
</CopyToClipboard>
<Radio.Group
value={jsonMode}
size="small"
onChange={onChangeJsonMode}
>
<Radio.Button value="view">预览</Radio.Button>
<Radio.Button value="code">编辑</Radio.Button>
</Radio.Group>
{jsonMode === "code" ? (
<Button
size="small"
type="primary"
onClick={onsubmit}
icon={<CopyOutlined alt="复制到剪切板" />}
>
保存
</Button>
) : null}
</div>
</div>
<div className={s.wrap} ref={container} />
</>
);
}
Example #27
Source File: MixedArguments.tsx From yugong with MIT License | 4 votes |
Mixedarguments: React.FC<Props> = ({ typeArguments, onChange, className }) => {
const [jsonData, setJsonData] = useState<AppDataLayoutItemTypes>();
const [jsonMode, setJsonMode] = useState<'view' | 'code'>('view');
useEffect(() => {
const result = cloneDeep(typeArguments);
setJsonData(result.data || {});
}, [typeArguments]);
const jsoneditor = useRef<JSONEditor>();
const container = useRef<any>();
const onsubmit = useCallback(() => {
try {
var json = jsoneditor.current?.get();
if (json && onChange instanceof Function) {
const result = cloneDeep(typeArguments);
result.data = json;
jsoneditor.current?.setMode('view');
setJsonMode('view');
onChange(result);
message.success(`${typeArguments.name}已更新!`);
}
} catch (e) {
message.error('保存失败!JSON数据格式不正确');
return;
}
}, [onChange, typeArguments]);
useEffect(() => {
if (container.current && jsonData) {
jsoneditor.current = new JSONEditor(container.current, {
mode: jsonMode,
mainMenuBar: false,
});
jsoneditor.current.set(jsonData);
}
return () => {
if (jsoneditor.current) {
jsoneditor.current.destroy();
}
};
}, [jsonData, jsonMode]);
const onChangeJsonMode = useCallback((e) => {
try {
var json = jsoneditor.current?.get();
if (json) {
jsoneditor.current?.setMode('code');
setJsonMode('code');
}
} catch (error) {
console.error(error);
}
}, []);
return (
<div className={classNames(className)}>
<div className={s.toolbar} >
<div>
<CopyToClipboard
text={JSON.stringify(jsonData)}
onCopy={() => message.info('已复制到剪切板')}
>
<Button size="small" icon={<CopyOutlined alt="复制到剪切板" />}>
复制
</Button>
</CopyToClipboard>
{jsonMode === 'view' ? (
<Button
size="small"
type="primary"
onClick={onChangeJsonMode}
icon={<FormOutlined alt="编辑JSON" />}
>
编辑
</Button>
) : null}
{jsonMode === 'code' ? (
<Button
size="small"
type="primary"
onClick={onsubmit}
icon={<SaveOutlined alt="保存JSON" />}
>
保存
</Button>
) : null}
</div>
</div>
<div className={s.wrap} ref={container} />
</div>
);
}
Example #28
Source File: CodeBlock.tsx From brilliant with MIT License | 4 votes |
CodeBlock: FC<CodeProps> = props => {
const [isOpen, setIsOpen] = useState(false);
const [isCopied, setIsCopied] = useState(false);
const [isDown, setIsDown] = useState(false);
const { blockProps, block } = props;
const {
getEditorState,
setEditorState,
readOnly,
languages,
renderLanguageSelect,
} = blockProps;
let { language } = blockProps;
language = alias[language] || language;
const selectedLabel = languages[language];
const selectedValue = language;
const copyValue = useMemo(() => (block as any).getText(), [block]);
const options = Object.keys(languages).reduce(
(acc, val) => [
...acc,
{
label: languages[val],
value: val,
},
],
[]
);
const onChange = (ev): void => {
ev.preventDefault();
ev.stopPropagation();
setIsOpen(false);
const blockKey = block.getKey();
const editorState = getEditorState;
const selection = editorState.getSelection();
const language = ev.currentTarget.value;
const blockSelection = selection.merge({
anchorKey: blockKey,
focusKey: blockKey,
});
let currentContent = editorState.getCurrentContent();
currentContent = Modifier.mergeBlockData(currentContent, blockSelection, {
language,
} as any);
const newEditorState = EditorState.push(
editorState,
currentContent,
'change-block-data'
);
setEditorState(newEditorState);
};
const onSelectClick = (ev): void => {
setIsOpen(true);
ev.stopPropagation();
};
const onClickOutside = (): void => {
if (isOpen === false) return;
setIsOpen(false);
const { getEditorState, setEditorState } = blockProps;
const editorState = getEditorState;
const selection = editorState.getSelection();
setEditorState(EditorState.forceSelection(editorState, selection));
};
const handleCopyCode = (e: Event) => {
e.stopPropagation();
e.preventDefault();
const plainText = (block as any).getText();
const oDiv = document.createElement('div');
oDiv.innerText = plainText;
document.body.appendChild(oDiv);
const range = document.createRange();
window.getSelection().removeAllRanges();
range.selectNode(oDiv);
oDiv.style.position = 'absolute';
oDiv.style.top = '9999';
window.getSelection().addRange(range);
document.execCommand('Copy');
oDiv.remove();
};
const handleMouseDown = (): void => {
toast('✅复制成功!', {
position: 'top-center',
autoClose: 700,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
setIsDown(true);
};
return (
<pre className="language-xxx">
<EditorBlock {...props} />
<CopyToClipboard
contentEditable={false}
text={copyValue}
onCopy={() => setIsCopied(true)}
>
<div
contentEditable={false}
className={Styles.copy}
onMouseDown={handleMouseDown}
onMouseUp={() => {
setIsDown(false);
}}
>
<RichIcon
type="icon-fuzhi"
contentEditable={false}
className={`${Styles.copyBtn} ${isDown ? Styles.btnDown : ''}`}
></RichIcon>
</div>
</CopyToClipboard>
<ToastContainer className={Styles.toast} />
{!readOnly && (
<SwitchContainer
onClickOutside={onClickOutside}
onClick={onSelectClick}
>
{renderLanguageSelect({
selectedLabel,
selectedValue,
onChange,
options,
})}
</SwitchContainer>
)}
</pre>
);
}
Example #29
Source File: Sider.tsx From your_spotify with GNU General Public License v3.0 | 4 votes |
export default function Sider({ className }: SiderProps) {
const dispatch = useAppDispatch();
const user = useSelector(selectUser);
const location = useLocation();
const inputRef = useRef<HTMLInputElement | null>(null);
const [search, setSearch] = useState('');
const results = useConditionalAPI(search.length >= 3, api.searchArtists, search);
const reset = useCallback(() => {
setSearch('');
}, []);
const copyCurrentPage = useCallback(() => {
if (!user?.publicToken) {
dispatch(
alertMessage({
level: 'error',
message: 'No public token generated, go to the settings page to generate one',
}),
);
return;
}
dispatch(
alertMessage({
level: 'info',
message: 'Copied current page to clipboard with public token',
}),
);
}, [dispatch, user?.publicToken]);
const toCopy = useShareLink();
return (
<div className={clsx(s.root, className)}>
<Link to="/" className={s.title}>
<Text onDark element="h1">
Your Spotify
</Text>
</Link>
<input
className={s.input}
placeholder="Search..."
value={search}
onChange={(ev) => setSearch(ev.target.value)}
ref={inputRef}
/>
<Popper
open={search.length > 0}
anchorEl={inputRef.current}
placement="bottom"
className={s.popper}>
<Paper className={s.results} style={{ width: inputRef.current?.clientWidth }}>
{search.length < 3 && <Text element="strong">At least 3 characters</Text>}
{results?.length === 0 && <Text element="strong">No results found</Text>}
{results?.map((res) => (
<Link to={`/artist/${res.id}`} className={s.result} key={res.id} onClick={reset}>
<img className={s.resultimage} src={getImage(res)} alt="Artist" />
<Text element="strong">{res.name}</Text>
</Link>
))}
</Paper>
</Popper>
<nav>
{links.map((category) => (
<div className={s.category} key={category.label}>
<Text element="div" onDark className={s.categoryname}>
{category.label}
</Text>
{toCopy &&
category.items.map((link) => {
if (link.link === '/share') {
return (
<CopyToClipboard key={link.label} onCopy={copyCurrentPage} text={toCopy}>
<div className={s.link} key={link.label}>
<Text onDark>
{location.pathname === link.link ? link.iconOn : link.icon}
</Text>
<Text onDark>{link.label}</Text>
</div>
</CopyToClipboard>
);
}
return (
<Link to={link.link} className={s.link} key={link.label}>
<Text onDark>{location.pathname === link.link ? link.iconOn : link.icon}</Text>
<Text onDark>{link.label}</Text>
</Link>
);
})}
</div>
))}
</nav>
</div>
);
}