wip: Implemented editor export
This commit is contained in:
parent
77ee973355
commit
3ec1cbd01b
10 changed files with 332 additions and 157 deletions
|
@ -27,7 +27,7 @@ export const CopyToClipboard = ({ content, label = false, sup = false }) => {
|
|||
const text =
|
||||
typeof content === 'string' ? content : strip(ReactDOMServer.renderToStaticMarkup(content))
|
||||
|
||||
const style = sup ? 'w-4 h-4 -mt-4' : 'w-5 h-5'
|
||||
const style = sup ? 'tw-w-4 tw-h-4 tw--mt-4' : 'tw-w-5 tw-h-5'
|
||||
|
||||
return (
|
||||
<Copy text={text} onCopy={() => handleCopied(setCopied, setLoadingStatus, label)}>
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import React, { useContext, useState } from 'react'
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import { horFlexClasses } from '@freesewing/utils'
|
||||
import { CopyIcon, OkIcon } from '@freesewing/react/components/Icon'
|
||||
import { CopyToClipboard as Copy } from 'react-copy-to-clipboard'
|
||||
|
||||
const strip = (html) =>
|
||||
typeof DOMParser === 'undefined'
|
||||
? html
|
||||
: new DOMParser().parseFromString(html, 'text/html').body.textContent || ''
|
||||
|
||||
const handleCopied = (setCopied, label, update) => {
|
||||
setCopied(true)
|
||||
update.notifySuccess(label ? `${label} c` : 'C' + 'opied to clipboard')
|
||||
setTimeout(() => setCopied(false), 1000)
|
||||
}
|
||||
|
||||
export const CopyToClipboard = ({ content, label = false, sup = false, update }) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const text =
|
||||
typeof content === 'string' ? content : strip(ReactDOMServer.renderToStaticMarkup(content))
|
||||
|
||||
const style = sup ? 'tw-w-4 tw-h-4 tw--mt-4' : 'tw-w-5 tw-h-5'
|
||||
|
||||
return (
|
||||
<Copy text={text} onCopy={() => handleCopied(setCopied, label, update)}>
|
||||
<button className={copied ? 'tw-text-success' : ''}>
|
||||
{copied ? (
|
||||
<OkIcon
|
||||
className={`${style} tw-text-success-content tw-bg-success tw-rounded-full tw-p-1`}
|
||||
stroke={4}
|
||||
/>
|
||||
) : (
|
||||
<CopyIcon className={`${style} tw-text-inherit`} />
|
||||
)}
|
||||
</button>
|
||||
</Copy>
|
||||
)
|
||||
}
|
||||
|
||||
export const CopyToClipboardButton = ({ content, label = false, update, children }) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const text =
|
||||
typeof content === 'string' ? content : strip(ReactDOMServer.renderToStaticMarkup(content))
|
||||
|
||||
const style = 'tw-w-6 tw-h-6'
|
||||
|
||||
return (
|
||||
<Copy text={text} onCopy={() => handleCopied(setCopied, label, update)}>
|
||||
<button
|
||||
className={`${horFlexClasses} tw-daisy-btn ${copied ? 'tw-daisy-btn-success' : 'tw-daisy-btn-primary'}`}
|
||||
>
|
||||
{copied ? (
|
||||
<OkIcon
|
||||
className={`${style} tw-text-success-content tw-bg-success tw-rounded-full tw-p-1`}
|
||||
stroke={4}
|
||||
/>
|
||||
) : (
|
||||
<CopyIcon className={`${style} tw-text-inherit`} />
|
||||
)}
|
||||
<div>{children}</div>
|
||||
</button>
|
||||
</Copy>
|
||||
)
|
||||
}
|
162
packages/react/components/Editor/components/views/ExportView.mjs
Normal file
162
packages/react/components/Editor/components/views/ExportView.mjs
Normal file
|
@ -0,0 +1,162 @@
|
|||
// Dependencies
|
||||
import { linkClasses, horFlexClasses, patternUrlFromState } from '@freesewing/utils'
|
||||
import { exportTypes, handleExport } from '../../lib/export/index.mjs'
|
||||
// Hooks
|
||||
import React, { useState } from 'react'
|
||||
// Components
|
||||
import { H1, H2, H3, H5 } from '@freesewing/react/components/Heading'
|
||||
import { Popout } from '@freesewing/react/components/Popout'
|
||||
import { HeaderMenu } from '../HeaderMenu.mjs'
|
||||
import { CopyToClipboardButton } from '../CopyToClipboard.mjs'
|
||||
import { Highlight } from '@freesewing/react/components/Highlight'
|
||||
import { EditIcon, CodeIcon, TipIcon, PrintIcon } from '@freesewing/react/components/Icon'
|
||||
|
||||
/**
|
||||
* This is the export view, it allows you to export your pattern in a variety of formats
|
||||
*
|
||||
* @param {Object} props - All the props
|
||||
* @param {Function} props.config - The editor configuration
|
||||
* @param {Object} props.state - The editor state object
|
||||
* @param {Object} props.update - Helper object for updating the editor state
|
||||
*/
|
||||
export const ExportView = (props) => {
|
||||
const { config, state, update } = props
|
||||
const [link, setLink] = useState(false)
|
||||
const [format, setFormat] = useState(false)
|
||||
|
||||
const { protocol, hostname, port } = window.location
|
||||
const site =
|
||||
(protocol === 'https:' && port === 443) || (protocol === 'http:' && port === 80)
|
||||
? `${window.location.protocol}//${window.location.hostname}`
|
||||
: `${window.location.protocol}//${window.location.hostname}:${window.location.port}`
|
||||
const urls = {
|
||||
a: `${site}${patternUrlFromState(state, true)}`,
|
||||
b: `${site}${patternUrlFromState(state, false)}`,
|
||||
}
|
||||
|
||||
const exportProps = {
|
||||
design: props.design,
|
||||
Design: props.Design,
|
||||
ui: props.state.ui,
|
||||
settings: props.state.settings,
|
||||
setLink,
|
||||
setFormat,
|
||||
startLoading: update.startLoading,
|
||||
stopLoading: update.stopLoading,
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderMenu state={state} {...{ config, update }} />
|
||||
<div className="tw-m-auto tw-mt-8 tw-max-w-2xl tw-px-4 tw-mb-8">
|
||||
<H1>Export Pattern</H1>
|
||||
<H2>Share your pattern</H2>
|
||||
<p>If you merely want to share your pattern with others, you can copy these URLs:</p>
|
||||
<div className="tw-grid tw-grid-cols-1 lg:tw-grid-cols-2 tw-gap-2 tw-mt-2 ">
|
||||
<CopyToClipboardButton content={urls.a} update={update}>
|
||||
Pattern and Measurements
|
||||
</CopyToClipboardButton>
|
||||
<CopyToClipboardButton content={urls.b} update={update}>
|
||||
Pattern only
|
||||
</CopyToClipboardButton>
|
||||
</div>
|
||||
<details className="tw-pt-2">
|
||||
<summary>
|
||||
<span className={linkClasses}>Explain these buttons</span>
|
||||
</summary>
|
||||
<div className="tw-ml-4 tw-border-l-2 tw-pl-4">
|
||||
<H5>Pattern and Measurements</H5>
|
||||
<p>
|
||||
Use the <b>Pattern and Measurements</b> URL to share this pattern exactly as-is,
|
||||
including the measurements used to generate it:
|
||||
</p>
|
||||
<Highlight noCopy title="URL" language="URL">
|
||||
{urls.a}
|
||||
</Highlight>
|
||||
<div className="tw-text-sm tw--mt-3 tw-flex tw-flex-row tw-gap-2 tw-items-center tw-pl-4 tw-mb-4">
|
||||
<TipIcon className="tw-w-5 tw-h-5 tw-text-success" />
|
||||
Use this to allow others to troubleshoot your pattern.
|
||||
</div>
|
||||
<H5>Pattern only</H5>
|
||||
<p>
|
||||
Use the <b>Pattern only</b> to share this pattern as-is, but without measurements:
|
||||
</p>
|
||||
<Highlight noCopy title="URL" language="URL">
|
||||
{urls.b}
|
||||
</Highlight>
|
||||
<div className="tw-text-sm tw--mt-3 tw-flex tw-flex-row tw-gap-2 tw-items-center tw-pl-4">
|
||||
<TipIcon className="tw-w-5 tw-h-5 tw-text-success" />
|
||||
Use this to allow others to recreate this pattern for themselves.
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<H2>Export for printing</H2>
|
||||
<p>You can export your pattern in a variety of page formats for printing:</p>
|
||||
<div className="tw-grid tw-grid-cols-1 lg:tw-grid-cols-2 tw-gap-2">
|
||||
<div className="tw-flex tw-flex-col tw-gap-2 tw-max-w-md">
|
||||
<H3>ISO paper sizes</H3>
|
||||
{['a4', 'a3', 'a2', 'a1', 'a0'].map((format) => (
|
||||
<button
|
||||
className={`${horFlexClasses} tw-daisy-btn tw-daisy-btn-primary tw-uppercase`}
|
||||
onClick={() => exportPattern({ ...exportProps, format })}
|
||||
>
|
||||
<PrintIcon />
|
||||
{format} PDF
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="tw-flex tw-flex-col tw-gap-2 tw-max-w-md">
|
||||
<H3>Other paper sizes</H3>
|
||||
{['letter', 'legal', 'tabloid'].map((format) => (
|
||||
<button
|
||||
className={`${horFlexClasses} tw-daisy-btn tw-daisy-btn-primary tw-uppercase`}
|
||||
onClick={() => exportPattern({ ...exportProps, format })}
|
||||
>
|
||||
<PrintIcon />
|
||||
{format} PDF
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<H2>Export for editing</H2>
|
||||
<p>
|
||||
We recommend SVG for editing, but we also provide a full-sized PDF if you prefer that.
|
||||
</p>
|
||||
<div className="tw-grid tw-grid-cols-1 lg:tw-grid-cols-2 tw-gap-2 tw-mt-2">
|
||||
{['svg', 'pdf'].map((format) => (
|
||||
<button
|
||||
className={`${horFlexClasses} tw-daisy-btn tw-daisy-btn-primary tw-uppercase`}
|
||||
onClick={() => exportPattern({ ...exportProps, format })}
|
||||
>
|
||||
<EditIcon />
|
||||
{format}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<H2>Export as code</H2>
|
||||
<p>This is all you need to reconstruct this pattern using FreeSewing' software:</p>
|
||||
<div className="tw-grid tw-grid-cols-1 lg:tw-grid-cols-2 tw-gap-2 tw-mt-2">
|
||||
{['json', 'yaml'].map((format) => (
|
||||
<button
|
||||
className={`${horFlexClasses} tw-daisy-btn tw-daisy-btn-primary tw-uppercase`}
|
||||
onClick={() => exportPattern({ ...exportProps, format })}
|
||||
>
|
||||
<CodeIcon />
|
||||
{format}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function exportPattern(props) {
|
||||
props.setLink(false)
|
||||
props.setFormat(props.format)
|
||||
handleExport({
|
||||
...props,
|
||||
onComplete: (e) => (e.data?.link ? props.setLink(e.data.link) : null),
|
||||
onError: (e) => (e.data?.error ? props.stopLoading('export', e.data.error.message) : null),
|
||||
})
|
||||
}
|
|
@ -4,6 +4,7 @@ import { DesignsView } from './DesignsView.mjs'
|
|||
import { MeasurementsView } from './MeasurementsView.mjs'
|
||||
import { DraftView } from './DraftView.mjs'
|
||||
import { SaveView } from './SaveView.mjs'
|
||||
import { ExportView } from './ExportView.mjs'
|
||||
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
||||
import {
|
||||
OptionsIcon,
|
||||
|
@ -53,6 +54,7 @@ export const View = (props) => {
|
|||
if (view === 'measurements') return <MeasurementsView {...props} />
|
||||
if (view === 'draft') return <DraftView {...props} />
|
||||
if (view === 'save') return <SaveView {...props} />
|
||||
if (view === 'export') return <ExportView {...props} />
|
||||
/*
|
||||
viewComponents: {
|
||||
draft: 'DraftView',
|
||||
|
|
|
@ -18,6 +18,7 @@ const defaultTitles = {
|
|||
* @params {bool} raw - Set this to true to not escape tags
|
||||
* @params {string} title - Title for the highlight
|
||||
* @params {string} copy - Content to copy to clipboard
|
||||
* @params {bool} noCopy - Do not add copy to clipboard
|
||||
*/
|
||||
export const Highlight = ({
|
||||
language = 'txt',
|
||||
|
@ -25,6 +26,7 @@ export const Highlight = ({
|
|||
raw = false,
|
||||
title = false,
|
||||
copy = false,
|
||||
noCopy = false,
|
||||
}) => {
|
||||
if (children?.props?.className) {
|
||||
language = children.props.className.split('-').pop()
|
||||
|
@ -46,7 +48,7 @@ export const Highlight = ({
|
|||
`}
|
||||
>
|
||||
<span>{label}</span>
|
||||
<CopyToClipboard content={copy ? copy : children} label={label} />
|
||||
{noCopy ? null : <CopyToClipboard content={copy ? copy : children} label={label} />}
|
||||
</div>
|
||||
<pre {...preProps}>{children}</pre>
|
||||
</div>
|
||||
|
|
|
@ -125,6 +125,13 @@ export const CloseIcon = (props) => (
|
|||
</IconWrapper>
|
||||
)
|
||||
|
||||
// Looks like coding brackets
|
||||
export const CodeIcon = (props) => (
|
||||
<IconWrapper {...props}>
|
||||
<path d="M17.25 6.75 22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3-4.5 16.5" />
|
||||
</IconWrapper>
|
||||
)
|
||||
|
||||
// FIXME
|
||||
export const CompareIcon = (props) => (
|
||||
<IconWrapper {...props}>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue