Merge branch 'copy-download-output' into 'dev'
Added copy and download to output. Improved tooltip component See merge request ligolang/ligo!426
This commit is contained in:
commit
2c3409f5cf
@ -7,6 +7,7 @@ import { Examples } from './components/examples';
|
||||
import { FloatButtonComponent } from './components/float-button';
|
||||
import { HeaderComponent } from './components/header';
|
||||
import { TabsPanelComponent } from './components/tabs-panel';
|
||||
import { TooltipContainer } from './components/tooltip';
|
||||
import configureStore from './configure-store';
|
||||
|
||||
const store = configureStore();
|
||||
@ -48,6 +49,7 @@ const App: React.FC = () => {
|
||||
href="https://discord.gg/9rhYaEt"
|
||||
></FloatButtonComponent>
|
||||
</FeedbackContainer>
|
||||
<TooltipContainer></TooltipContainer>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Tooltip } from './tooltip';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
@ -36,46 +38,16 @@ const Button = styled.a`
|
||||
}
|
||||
`;
|
||||
|
||||
const Tooltip = styled.div<{ visible?: boolean }>`
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
white-space: nowrap;
|
||||
transform: translateX(-6.5em);
|
||||
|
||||
font-size: var(--font_sub_size);
|
||||
color: var(--tooltip_foreground);
|
||||
background-color: var(--tooltip_background);
|
||||
border-radius: 6px;
|
||||
padding: 5px 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease 0.2s;
|
||||
|
||||
${props =>
|
||||
props.visible &&
|
||||
css`
|
||||
opacity: 1;
|
||||
`}
|
||||
`;
|
||||
|
||||
export const FloatButtonComponent = (props: {
|
||||
tooltip: string;
|
||||
text: string;
|
||||
href: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [isTooltipShowing, setShowTooltip] = useState(false);
|
||||
|
||||
return (
|
||||
<Container className={props.className}>
|
||||
<Tooltip visible={isTooltipShowing}>{props.tooltip}</Tooltip>
|
||||
<Button
|
||||
onMouseOver={() => setShowTooltip(true)}
|
||||
onMouseOut={() => setShowTooltip(false)}
|
||||
href={props.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Tooltip position="left">{props.tooltip}</Tooltip>
|
||||
<Button href={props.href} target="_blank" rel="noopener noreferrer">
|
||||
{props.text}
|
||||
</Button>
|
||||
</Container>
|
||||
|
@ -7,6 +7,7 @@ import { AppState } from '../redux/app';
|
||||
import { CommandState } from '../redux/command';
|
||||
import { DoneLoadingAction, LoadingState } from '../redux/loading';
|
||||
import { ResultState } from '../redux/result';
|
||||
import { OutputToolbarComponent } from './output-toolbar';
|
||||
|
||||
const Container = styled.div<{ visible?: boolean }>`
|
||||
position: absolute;
|
||||
@ -15,8 +16,8 @@ const Container = styled.div<{ visible?: boolean }>`
|
||||
height: 100%;
|
||||
|
||||
font-family: Menlo, Monaco, 'Courier New', monospace;
|
||||
overflow: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.2s ease-in;
|
||||
@ -42,9 +43,9 @@ const CancelButton = styled.div`
|
||||
|
||||
const Output = styled.div`
|
||||
flex: 1;
|
||||
padding: 0.8em;
|
||||
padding: 0 0.5em 0.5em 0.5em;
|
||||
display: flex;
|
||||
|
||||
overflow: scroll;
|
||||
/* This font size is used to calcuate spinner size */
|
||||
font-size: 1em;
|
||||
`;
|
||||
@ -65,6 +66,37 @@ const Pre = styled.pre`
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
function copyOutput(el: HTMLElement | null) {
|
||||
if (el) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(el);
|
||||
|
||||
const selection = window.getSelection();
|
||||
|
||||
if (selection) {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadOutput(el: HTMLElement | null) {
|
||||
if (el) {
|
||||
const anchor = document.createElement('a');
|
||||
anchor.setAttribute(
|
||||
'href',
|
||||
'data:text/plain;charset=utf-8,' + encodeURIComponent(el.innerHTML)
|
||||
);
|
||||
anchor.setAttribute('download', 'output.txt');
|
||||
|
||||
anchor.style.display = 'none';
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
}
|
||||
}
|
||||
|
||||
export const OutputTabComponent = (props: {
|
||||
selected?: boolean;
|
||||
onCancel?: () => void;
|
||||
@ -85,13 +117,14 @@ export const OutputTabComponent = (props: {
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const outputRef = useRef(null);
|
||||
const outputRef = useRef<HTMLDivElement>(null);
|
||||
const preRef = useRef<HTMLPreElement>(null);
|
||||
const [spinnerSize, setSpinnerSize] = useState(50);
|
||||
|
||||
useEffect(() => {
|
||||
const htmlElement = (outputRef.current as unknown) as HTMLElement;
|
||||
const outputEl = (outputRef.current as unknown) as HTMLElement;
|
||||
const fontSize = window
|
||||
.getComputedStyle(htmlElement, null)
|
||||
.getComputedStyle(outputEl, null)
|
||||
.getPropertyValue('font-size');
|
||||
|
||||
setSpinnerSize(parseFloat(fontSize) * 3);
|
||||
@ -99,6 +132,12 @@ export const OutputTabComponent = (props: {
|
||||
|
||||
return (
|
||||
<Container visible={props.selected}>
|
||||
{output.length !== 0 && (
|
||||
<OutputToolbarComponent
|
||||
onCopy={() => copyOutput(preRef.current)}
|
||||
onDownload={() => downloadOutput(preRef.current)}
|
||||
></OutputToolbarComponent>
|
||||
)}
|
||||
<Output id="output" ref={outputRef}>
|
||||
{loading.loading && (
|
||||
<LoadingContainer>
|
||||
@ -122,7 +161,7 @@ export const OutputTabComponent = (props: {
|
||||
</LoadingContainer>
|
||||
)}
|
||||
{!loading.loading &&
|
||||
((output.length !== 0 && <Pre>{output}</Pre>) ||
|
||||
((output.length !== 0 && <Pre ref={preRef}>{output}</Pre>) ||
|
||||
(contract.length !== 0 && (
|
||||
<span>
|
||||
The contract was successfully deployed to the babylonnet test
|
||||
|
@ -0,0 +1,78 @@
|
||||
import { faCopy, faDownload } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Tooltip } from './tooltip';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 0.2em 0.5em;
|
||||
z-index: 3;
|
||||
`;
|
||||
|
||||
const Action = styled.div`
|
||||
z-index: 3;
|
||||
position: relative;
|
||||
margin: 4px 6px;
|
||||
cursor: pointer;
|
||||
|
||||
opacity: 0.5;
|
||||
color: #444;
|
||||
|
||||
::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
right: -4px;
|
||||
top: -4px;
|
||||
border-radius: 4px;
|
||||
background: none;
|
||||
box-sizing: border-box;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transition-property: transform, opacity;
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
:hover::before {
|
||||
background-color: rgba(32, 33, 36, 0.059);
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const OutputToolbarComponent = (props: {
|
||||
onCopy?: () => void;
|
||||
onDownload?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<Container>
|
||||
<Action onClick={() => props.onCopy && props.onCopy()}>
|
||||
<FontAwesomeIcon icon={faCopy}></FontAwesomeIcon>
|
||||
<Tooltip>Copy</Tooltip>
|
||||
</Action>
|
||||
<Action onClick={() => props.onDownload && props.onDownload()}>
|
||||
<FontAwesomeIcon icon={faDownload}></FontAwesomeIcon>
|
||||
<Tooltip>Download</Tooltip>
|
||||
</Action>
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -8,6 +8,7 @@ import styled, { css } from 'styled-components';
|
||||
import { AppState } from '../redux/app';
|
||||
import { ChangeShareLinkAction, ShareState } from '../redux/share';
|
||||
import { share } from '../services/api';
|
||||
import { Tooltip } from './tooltip';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
@ -96,26 +97,6 @@ const Input = styled.input<{ visible?: boolean }>`
|
||||
`}
|
||||
`;
|
||||
|
||||
const Tooltip = styled.div<{ visible?: boolean }>`
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
transform: translateY(2.5em);
|
||||
font-size: var(--font_sub_size);
|
||||
color: var(--tooltip_foreground);
|
||||
background-color: var(--tooltip_background);
|
||||
border-radius: 6px;
|
||||
padding: 5px 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease 0.2s;
|
||||
|
||||
${props =>
|
||||
props.visible &&
|
||||
css`
|
||||
opacity: 1;
|
||||
`}
|
||||
`;
|
||||
|
||||
const shareAction = () => {
|
||||
return async function(dispatch: Dispatch, getState: () => AppState) {
|
||||
try {
|
||||
@ -138,7 +119,6 @@ export const ShareComponent = () => {
|
||||
state => state.share.link
|
||||
);
|
||||
const [clicked, setClicked] = useState(false);
|
||||
const [isTooltipShowing, setShowTooltip] = useState(false);
|
||||
|
||||
const SHARE_TOOLTIP = 'Share code';
|
||||
const COPY_TOOLTIP = 'Copy link';
|
||||
@ -149,14 +129,12 @@ export const ShareComponent = () => {
|
||||
if (shareLink) {
|
||||
if (inputEl.current && copy(inputEl.current)) {
|
||||
setTooltipMessage(COPIED_TOOLTIP);
|
||||
setShowTooltip(true);
|
||||
} else {
|
||||
setClicked(true);
|
||||
setTooltipMessage(COPY_TOOLTIP);
|
||||
}
|
||||
} else {
|
||||
setClicked(false);
|
||||
setShowTooltip(false);
|
||||
setTooltipMessage(SHARE_TOOLTIP);
|
||||
}
|
||||
}, [shareLink]);
|
||||
@ -177,9 +155,7 @@ export const ShareComponent = () => {
|
||||
if (tooltipMessage === COPIED_TOOLTIP) {
|
||||
setTooltipMessage(COPY_TOOLTIP);
|
||||
}
|
||||
setShowTooltip(true);
|
||||
}}
|
||||
onMouseOut={() => setShowTooltip(false)}
|
||||
onClick={() => {
|
||||
if (!shareLink) {
|
||||
dispatch(shareAction());
|
||||
@ -193,7 +169,7 @@ export const ShareComponent = () => {
|
||||
>
|
||||
<Label visible={!clicked}>Share</Label>
|
||||
<Copy visible={clicked}></Copy>
|
||||
<Tooltip visible={isTooltipShowing}>{tooltipMessage}</Tooltip>
|
||||
<Tooltip>{tooltipMessage}</Tooltip>
|
||||
</Button>
|
||||
</Container>
|
||||
);
|
||||
|
@ -69,7 +69,11 @@ export const TabsPanelComponent = () => {
|
||||
<Container>
|
||||
<Header>
|
||||
{TABS.map(tab => (
|
||||
<Tab id={tab.id} selected={selectedTab.index === tab.index}>
|
||||
<Tab
|
||||
key={tab.id}
|
||||
id={tab.id}
|
||||
selected={selectedTab.index === tab.index}
|
||||
>
|
||||
<Label onClick={() => selectTab(tab)}>{tab.label}</Label>
|
||||
</Tab>
|
||||
))}
|
||||
|
104
tools/webide/packages/client/src/components/tooltip.tsx
Normal file
104
tools/webide/packages/client/src/components/tooltip.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import React, { createElement, useEffect, useRef, useState } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Container = styled.div`
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
export const StyledTooltip = styled.div<{
|
||||
visible: boolean;
|
||||
x: string;
|
||||
y: string;
|
||||
}>`
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: 1001;
|
||||
font-size: var(--font_sub_size);
|
||||
color: var(--tooltip_foreground);
|
||||
background-color: var(--tooltip_background);
|
||||
border-radius: 6px;
|
||||
padding: 5px 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease 0.2s;
|
||||
transform-origin: center;
|
||||
|
||||
${({ x, y }) => `transform: translate(calc(${x}), calc(${y}));`}
|
||||
${({ visible }) => visible && `opacity: 1;`}
|
||||
`;
|
||||
|
||||
const TOOLTIP_CONTAINER_ID = 'tooltip-container';
|
||||
type Position = 'top' | 'bottom' | 'left' | 'right';
|
||||
|
||||
export const TooltipContainer = () => {
|
||||
return <Container id={TOOLTIP_CONTAINER_ID}></Container>;
|
||||
};
|
||||
|
||||
function calcX(triggerRect: ClientRect, position?: Position) {
|
||||
if ('left' === position) {
|
||||
return `${triggerRect.left - 10}px - 100%`;
|
||||
} else if ('right' === position) {
|
||||
return `${triggerRect.right + 10}px`;
|
||||
}
|
||||
|
||||
return `${triggerRect.left + triggerRect.width / 2}px - 50%`;
|
||||
}
|
||||
|
||||
function calcY(triggerRect: ClientRect, position?: string) {
|
||||
if ('top' === position) {
|
||||
return `${triggerRect.top - 10}px - 100%`;
|
||||
} else if (!position || 'bottom' === position) {
|
||||
return `${triggerRect.bottom + 10}px`;
|
||||
}
|
||||
|
||||
return `${triggerRect.top + triggerRect.height / 2}px - 50%`;
|
||||
}
|
||||
|
||||
export const Tooltip = (props: { position?: Position; children: any }) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [isTooltipVisible, setTooltipVisible] = useState(false);
|
||||
|
||||
const renderTooltip = (visible: boolean, triggerRect: ClientRect) => {
|
||||
const tooltip = createElement(
|
||||
StyledTooltip,
|
||||
{
|
||||
visible,
|
||||
x: calcX(triggerRect, props.position),
|
||||
y: calcY(triggerRect, props.position)
|
||||
},
|
||||
props.children
|
||||
);
|
||||
|
||||
render(tooltip, document.getElementById(TOOLTIP_CONTAINER_ID));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
const trigger = (ref.current as HTMLElement).parentElement;
|
||||
|
||||
if (trigger) {
|
||||
if (isTooltipVisible) {
|
||||
renderTooltip(true, trigger.getBoundingClientRect());
|
||||
}
|
||||
|
||||
trigger.onmouseenter = _ => {
|
||||
renderTooltip(true, trigger.getBoundingClientRect());
|
||||
setTooltipVisible(true);
|
||||
};
|
||||
|
||||
trigger.onmouseleave = _ => {
|
||||
renderTooltip(false, trigger.getBoundingClientRect());
|
||||
setTooltipVisible(false);
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return <div ref={ref}></div>;
|
||||
};
|
@ -50,6 +50,7 @@
|
||||
|
||||
--tooltip_foreground: white;
|
||||
--tooltip_background: rgba(0, 0, 0, 0.75) /*#404040*/;
|
||||
--label_foreground: rgba(153, 153, 153, 1);
|
||||
}
|
||||
|
||||
body {
|
||||
|
Loading…
Reference in New Issue
Block a user