New copy action menu (#970)

* move copy actions to a new menu

* clean up copy menu

* clean up export menu

* move flex into reset styles

* use real ellipses

* fix copy menu id and classNames

Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
main
Michael Fix 5 years ago committed by GitHub
parent 1cf04495ab
commit 3acbd9ef9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -119,7 +119,7 @@ function Billing(props) {
}} }}
/> />
<hr /> <hr />
<Input placeholder="Cardholders's name..." name="name" required /> <Input placeholder="Cardholders's name" name="name" required />
</fieldset> </fieldset>
<small> <small>
(By clicking subscribe, you are accepting the{' '} (By clicking subscribe, you are accepting the{' '}
@ -135,7 +135,7 @@ function Billing(props) {
type="submit" type="submit"
color="rgba(255, 255, 255, 0.7)" color="rgba(255, 255, 255, 0.7)"
> >
{loading ? 'Sending...' : 'Subscribe'} {loading ? 'Sending' : 'Subscribe'}
</Button> </Button>
<div className={`error ${error ? 'visible' : ''}`} role="alert"> <div className={`error ${error ? 'visible' : ''}`} role="alert">
{X} {X}

@ -0,0 +1,120 @@
import React from 'react'
import { withRouter } from 'next/router'
import { useCopyTextHandler, useAsyncCallback } from 'actionsack'
import morph from 'morphmorph'
import { COLORS } from '../lib/constants'
import Button from './Button'
import Popout, { managePopout } from './Popout'
import CopySVG from './svg/Copy'
const toIFrame = url =>
`<iframe
src="${location.origin}/embed${url.replace(/^\/\?/, '?')}"
style="transform:scale(0.7); width:1024px; height:473px; border:0; overflow:hidden;"
sandbox="allow-scripts allow-same-origin">
</iframe>
`
const toURL = url => `${location.origin}${url}`
const toEncodedURL = morph.compose(encodeURI, toURL)
function CopyButton(props) {
return (
<Button
{...props}
hoverColor={COLORS.SECONDARY}
color="rgba(255, 255, 255, 0.7)"
padding="8px"
/>
)
}
const CopyEmbed = withRouter(({ router: { asPath }, mapper, title }) => {
const text = React.useMemo(() => mapper(asPath), [mapper, asPath])
const { onClick, copied } = useCopyTextHandler(text)
return <CopyButton onClick={onClick}>{copied ? 'Copied!' : title}</CopyButton>
})
const popoutStyle = { width: '140px', right: 0 }
function useClipboardSupport() {
const [isClipboardSupports, setClipboardSupport] = React.useState(false)
React.useEffect(() => {
setClipboardSupport(
window.navigator && window.navigator.clipboard && typeof ClipboardItem === 'function'
)
}, [])
return isClipboardSupports
}
function CopyMenu({ isVisible, toggleVisibility, copyImage }) {
const clipboardSupported = useClipboardSupport()
const [copy, { loading }] = useAsyncCallback(copyImage)
return (
<div className="copy-menu-container">
<div className="flex">
<Button
center
border
large
padding="0 16px"
margin="0 8px 0 0"
onClick={toggleVisibility}
color={COLORS.SECONDARY}
>
<CopySVG size={16} color={COLORS.SECONDARY} />
</Button>
</div>
<Popout
hidden={!isVisible}
borderColor={COLORS.SECONDARY}
pointerRight="24px"
style={popoutStyle}
>
<div className="copy-row flex">
<span>Copy to clipboard</span>
{clipboardSupported && (
<CopyButton id="export-clipboard" onClick={copy} disabled={loading}>
{loading ? 'Copying…' : 'Image'}
</CopyButton>
)}
<CopyEmbed title="Medium.com" mapper={toEncodedURL} />
<CopyEmbed title="IFrame" mapper={toIFrame} />
<CopyEmbed title="Plain URL" mapper={toURL} />
</div>
</Popout>
<style jsx>
{`
.copy-menu-container {
position: relative;
color: ${COLORS.SECONDARY};
flex: 1;
}
.copy-row {
flex-direction: column;
justify-content: space-between;
border-bottom: 1px solid ${COLORS.SECONDARY};
}
.copy-row :global(button) {
border-top: 1px solid ${COLORS.SECONDARY};
}
.copy-row > span {
padding: 8px;
margin: 0 auto;
}
`}
</style>
</div>
)
}
export default managePopout(React.memo(CopyMenu))

@ -14,6 +14,7 @@ import Overlay from './Overlay'
import BackgroundSelect from './BackgroundSelect' import BackgroundSelect from './BackgroundSelect'
import Carbon from './Carbon' import Carbon from './Carbon'
import ExportMenu from './ExportMenu' import ExportMenu from './ExportMenu'
import CopyMenu from './CopyMenu'
import Themes from './Themes' import Themes from './Themes'
import TweetButton from './TweetButton' import TweetButton from './TweetButton'
import FontFace from './FontFace' import FontFace from './FontFace'
@ -413,11 +414,11 @@ class Editor extends React.Component {
/> />
<div id="style-editor-button" /> <div id="style-editor-button" />
<div className="buttons"> <div className="buttons">
<CopyMenu copyImage={this.copyImage} />
<TweetButton onClick={this.upload} /> <TweetButton onClick={this.upload} />
<ExportMenu <ExportMenu
onChange={this.updateSetting} onChange={this.updateSetting}
exportImage={this.exportImage} exportImage={this.exportImage}
copyImage={this.copyImage}
exportSize={exportSize} exportSize={exportSize}
backgroundImage={backgroundImage} backgroundImage={backgroundImage}
/> />

@ -1,22 +1,11 @@
import React from 'react' import React from 'react'
import { withRouter } from 'next/router' import { useOnline, useKeyboardListener, useAsyncCallback } from 'actionsack'
import { useCopyTextHandler, useOnline, useKeyboardListener, useAsyncCallback } from 'actionsack'
import { COLORS, EXPORT_SIZES } from '../lib/constants' import { COLORS, EXPORT_SIZES } from '../lib/constants'
import Button from './Button' import Button from './Button'
import Input from './Input' import Input from './Input'
import Popout, { managePopout } from './Popout' import Popout, { managePopout } from './Popout'
const toIFrame = url =>
`<iframe
src="${location.origin}/embed${url.replace(/^\/\?/, '?')}"
style="transform:scale(0.7); width:1024px; height:473px; border:0; overflow:hidden;"
sandbox="allow-scripts allow-same-origin">
</iframe>
`
const toURL = url => encodeURI(`${location.origin}${url}`)
const MAX_PAYLOAD_SIZE = 5e6 // bytes const MAX_PAYLOAD_SIZE = 5e6 // bytes
function verifyPayloadSize(str) { function verifyPayloadSize(str) {
if (typeof str !== 'string') return true if (typeof str !== 'string') return true
@ -28,25 +17,7 @@ function verifyPayloadSize(str) {
return Buffer.byteLength(str, 'utf8') return Buffer.byteLength(str, 'utf8')
} }
const CopyEmbed = withRouter(({ router: { asPath }, mapper, title, margin }) => { const popoutStyle = { width: '240px', right: 0 }
const text = React.useMemo(() => mapper(asPath), [mapper, asPath])
const { onClick, copied } = useCopyTextHandler(text)
return (
<Button
onClick={onClick}
center
hoverColor={COLORS.PURPLE}
color={COLORS.DARK_PURPLE}
margin={margin}
style={{ minWidth: 48 }}
>
{copied ? 'Copied!' : title}
</Button>
)
})
const popoutStyle = { width: '350px', right: 0 }
function useSafari() { function useSafari() {
const [isSafari, setSafari] = React.useState(false) const [isSafari, setSafari] = React.useState(false)
@ -61,40 +32,24 @@ function useSafari() {
return isSafari return isSafari
} }
function useClipboardSupport() {
const [isClipboardSupports, setClipboardSupport] = React.useState(false)
React.useEffect(() => {
setClipboardSupport(
window.navigator && window.navigator.clipboard && typeof ClipboardItem === 'function'
)
}, [])
return isClipboardSupports
}
function ExportMenu({ function ExportMenu({
backgroundImage, backgroundImage,
onChange, onChange,
exportSize, exportSize,
isVisible, isVisible,
toggleVisibility, toggleVisibility,
exportImage: exp, exportImage: exp
copyImage
}) { }) {
const tooLarge = React.useMemo(() => !verifyPayloadSize(backgroundImage), [backgroundImage]) const tooLarge = React.useMemo(() => !verifyPayloadSize(backgroundImage), [backgroundImage])
const online = useOnline() const online = useOnline()
const isSafari = useSafari() const isSafari = useSafari()
const input = React.useRef()
const [exportImage, { loading }] = useAsyncCallback(exp) const [exportImage, { loading }] = useAsyncCallback(exp)
useKeyboardListener('⌘-⇧-e', () => exportImage()) useKeyboardListener('⌘-⇧-e', () => exportImage())
const disablePNG = isSafari && (tooLarge || !online) const disablePNG = isSafari && (tooLarge || !online)
const clipboardSupported = useClipboardSupport()
const input = React.useRef()
const handleExportSizeChange = selectedSize => () => onChange('exportSize', selectedSize) const handleExportSizeChange = selectedSize => () => onChange('exportSize', selectedSize)
const handleExport = format => () => const handleExport = format => () =>
@ -116,13 +71,13 @@ function ExportMenu({
data-cy="export-button" data-cy="export-button"
style={{ width: 92 }} style={{ width: 92 }}
> >
{loading ? 'Exporting...' : 'Export'} {loading ? 'Exporting' : 'Export'}
</Button> </Button>
</div> </div>
<Popout <Popout
hidden={!isVisible} hidden={!isVisible}
borderColor={COLORS.PURPLE} borderColor={COLORS.PURPLE}
pointerRight="28px" pointerRight="36px"
style={popoutStyle} style={popoutStyle}
> >
<div className="export-row"> <div className="export-row">
@ -147,31 +102,11 @@ function ExportMenu({
</div> </div>
</div> </div>
<div className="export-row"> <div className="export-row">
{/* IDEA: Remove open button if clipboardSupported? */}
<Button center color={COLORS.PURPLE} onClick={handleExport('open')}> <Button center color={COLORS.PURPLE} onClick={handleExport('open')}>
Open Open
</Button> </Button>
<div className="save-container"> <div className="save-container">
<span>Copy to clipboard</span> <span>Download</span>
<div>
<CopyEmbed title="URL" mapper={toURL} margin="0 8px 0 0" />
<CopyEmbed title="IFrame" mapper={toIFrame} margin="0 12px 0 0px" />
{clipboardSupported && (
<Button
center
hoverColor={COLORS.PURPLE}
color={COLORS.DARK_PURPLE}
onClick={copyImage}
id="export-clipboard"
disabled={loading}
>
Image
</Button>
)}
</div>
</div>
<div className="save-container">
<span>Export</span>
<div> <div>
{!disablePNG && ( {!disablePNG && (
<Button <Button
@ -208,11 +143,6 @@ function ExportMenu({
flex: 1; flex: 1;
} }
.flex {
display: flex;
height: 100%;
}
.export-row { .export-row {
display: flex; display: flex;
align-items: center; align-items: center;
@ -245,7 +175,6 @@ function ExportMenu({
display: flex; display: flex;
flex: 1; flex: 1;
} }
.save-container:first-of-type { .save-container:first-of-type {
padding: 12px 12px; padding: 12px 12px;
border-right: 1px solid ${COLORS.PURPLE}; border-right: 1px solid ${COLORS.PURPLE};

@ -209,7 +209,7 @@ export default class ImagePicker extends React.Component {
/> />
) : ( ) : (
<form onSubmit={this.handleURLInput}> <form onSubmit={this.handleURLInput}>
<Input type="text" title="Background Image" placeholder="Image URL..." align="left" /> <Input type="text" title="Background Image" placeholder="Image URL" align="left" />
<button type="submit">Upload</button> <button type="submit">Upload</button>
</form> </form>
)} )}

@ -9,7 +9,7 @@ import { useAuth } from './AuthContext'
function Drawer(props) { function Drawer(props) {
return ( return (
<Popout hidden={!props.isVisible} pointerRight="14px" style={{ width: '160px', right: 0 }}> <Popout hidden={!props.isVisible} pointerRight="14px" style={{ width: '160px', right: 0 }}>
<div className="flex"> <div className="flex column">
<Link href="/snippets"> <Link href="/snippets">
<Button large center padding="0.5rem 0" style={{ borderBottom: '1px solid' }}> <Button large center padding="0.5rem 0" style={{ borderBottom: '1px solid' }}>
<img src="/static/svg/snippets.svg" alt="Snippets page" width="16px" /> Snippets{' '} <img src="/static/svg/snippets.svg" alt="Snippets page" width="16px" /> Snippets{' '}
@ -32,10 +32,8 @@ function Drawer(props) {
</div> </div>
<style jsx> <style jsx>
{` {`
.flex { .column {
display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
} }
img { img {
position: relative; position: relative;

@ -23,7 +23,7 @@ function DeleteButton(props) {
onClick={onClick} onClick={onClick}
style={{ color: COLORS.RED }} style={{ color: COLORS.RED }}
> >
{loading ? 'Deleting...' : 'Delete'} {loading ? 'Deleting' : 'Delete'}
</ConfirmButton> </ConfirmButton>
) )
} }
@ -43,7 +43,7 @@ function DuplicateButton(props) {
color="#37b589" color="#37b589"
onClick={onClick} onClick={onClick}
> >
{loading ? 'Duplicating...' : 'Duplicate'} {loading ? 'Duplicating' : 'Duplicate'}
</Button> </Button>
) )
} }

@ -29,7 +29,7 @@ function TweetButton(props) {
onClick={onClick} onClick={onClick}
color={COLORS.BLUE} color={COLORS.BLUE}
> >
{loading ? 'Loading...' : 'Tweet'} {loading ? 'Loading' : 'Tweet'}
</Button> </Button>
) )
} }

@ -233,6 +233,11 @@ export default () => (
display: flex; display: flex;
} }
.flex {
display: flex;
height: 100%;
}
.capitalize { .capitalize {
text-transform: capitalize; text-transform: capitalize;
} }

@ -216,7 +216,7 @@ function SnippetsPage() {
Router.push('/') Router.push('/')
}} }}
> >
<h4>{loading ? 'Loading...' : !snippets.length ? 'Create snippet +' : 'Load more +'}</h4> <h4>{loading ? 'Loading' : !snippets.length ? 'Create snippet +' : 'Load more +'}</h4>
</ActionButton> </ActionButton>
)} )}
<style jsx> <style jsx>

Loading…
Cancel
Save