Add external resource flow

related to #360
This commit is contained in:
Kurt Hutten
2021-06-13 17:08:37 +10:00
parent da81942adc
commit 5083d8e7f8
4 changed files with 150 additions and 11 deletions

View File

@@ -0,0 +1,131 @@
import { useState } from 'react'
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
import OutBound from 'src/components/OutBound/OutBound'
import { prepareEncodedUrl, makeExternalUrl } from './helpers'
import { copyTextToClipboard } from 'src/helpers/clipboard'
import { useRender } from 'src/components/IdeWrapper/useRender'
import { toast } from '@redwoodjs/web/toast'
const ideTypeNameMap = {
openScad: 'OpenSCAD',
cadQuery: 'CadQuery',
}
const ExternalScript = () => {
const { state, thunkDispatch } = useIdeContext()
const handleRender = useRender()
const [rawUrl, setRawUrl] = useState('')
const [script, setScript] = useState('')
const [asyncState, setAsyncState] =
useState<'INIT' | 'SUCCESS' | 'ERROR' | 'LOADING'>('INIT')
const cadName = ideTypeNameMap[state.ideType]
const onPaste: React.ClipboardEventHandler<HTMLInputElement> = async ({
clipboardData,
}) => {
const url = clipboardData.getData('Text')
processUserUrl(url)
}
const onChange: React.ChangeEventHandler<HTMLInputElement> = async ({
target,
}) => setRawUrl(target.value)
const onKeyDown = async ({ key, target }) =>
key === 'Enter' && processUserUrl(target.value)
async function processUserUrl(url: string) {
setRawUrl(url)
try {
setAsyncState('LOADING')
const response = await fetch(prepareEncodedUrl(url))
if (response.status === 404) throw new Error("couldn't find script")
const script2 = await response.text()
if (script2.startsWith('<!DOCTYPE html>'))
throw new Error('got html document, not a script')
setScript(script2)
setAsyncState('SUCCESS')
} catch (e) {
setAsyncState('ERROR')
toast.error(
"We had trouble with you're URL, are you sure it was correct?"
)
}
}
const onCopyRender: React.MouseEventHandler<HTMLButtonElement> = () => {
copyTextToClipboard(makeExternalUrl(rawUrl))
thunkDispatch({ type: 'updateCode', payload: script })
setTimeout(handleRender)
}
return (
<div className="p-4">
<p className="text-sm pb-4">
Paste an external url containing a {cadName} script to generate a new
CadHub url for this resource.{' '}
<OutBound
className="underline text-gray-500"
to="TODO learn.cadhub link"
>
Learn more
</OutBound>{' '}
about this feature.
</p>
{['INIT', 'ERROR'].includes(asyncState) && (
<>
<p>Paste url</p>
<input
className="p-1 text-xs rounded border border-gray-700 w-full"
value={rawUrl}
onChange={onChange}
onPaste={onPaste}
onKeyDown={onKeyDown}
/>
</>
)}
{asyncState === 'ERROR' && (
<p className="text-sm text-red-800">That didn't work, try again.</p>
)}
{asyncState === 'LOADING' && (
<div className="h-10 relative">
<div className="inset-0 absolute flex items-center justify-center">
<div className="h-6 w-6 bg-pink-600 rounded-full animate-ping"></div>
</div>
</div>
)}
{asyncState === 'SUCCESS' && (
<>
<input
value={makeExternalUrl(rawUrl).replace(/^.+:\/\//g, '')}
readOnly
className="p-1 mt-4 text-xs rounded-t border border-gray-700 w-full"
/>
<button
className="w-full bg-gray-700 py-1 rounded-b text-gray-300"
onClick={() => copyTextToClipboard(makeExternalUrl(rawUrl))}
>
Copy URL
</button>
<div className="flex flex-col gap-2 pt-2">
<button
className="bg-gray-500 p-1 px-2 rounded text-gray-300"
onClick={onCopyRender}
>
Copy &amp; Render
</button>
<button
className="bg-gray-500 p-1 px-2 rounded text-gray-300"
onClick={() => {
setAsyncState('INIT')
setRawUrl('')
setScript('')
}}
>
Create another URL
</button>
</div>
</>
)}
</div>
)
}
export default ExternalScript

View File

@@ -6,7 +6,7 @@ const FullScriptEncoding = () => {
const { state } = useIdeContext() const { state } = useIdeContext()
const encodedLink = makeEncodedLink(state.code) const encodedLink = makeEncodedLink(state.code)
return ( return (
<> <div className="p-4">
<p className="text-sm pb-4 border-b border-gray-700"> <p className="text-sm pb-4 border-b border-gray-700">
Encodes your CodeCad script into a URL so that you can share your work Encodes your CodeCad script into a URL so that you can share your work
</p> </p>
@@ -21,7 +21,7 @@ const FullScriptEncoding = () => {
> >
Copy URL Copy URL
</button> </button>
</> </div>
) )
} }

View File

@@ -10,11 +10,6 @@ const scriptKey = 'encoded_script'
const scriptKeyV2 = 'encoded_script_v2' const scriptKeyV2 = 'encoded_script_v2'
const fetchText = 'fetch_text_v1' const fetchText = 'fetch_text_v1'
export function makeEncodedLink(code: string): string {
const encodedScript = encode(code)
return `${location.origin}${location.pathname}#${scriptKeyV2}=${encodedScript}`
}
export const githubSafe = (url: string): string => export const githubSafe = (url: string): string =>
url.includes('github.com') url.includes('github.com')
? url ? url
@@ -22,7 +17,20 @@ export const githubSafe = (url: string): string =>
.replace('/blob/', '/') .replace('/blob/', '/')
: url : url
const prepareEncodedUrl = flow(decodeURIComponent, githubSafe) export const prepareEncodedUrl = flow(decodeURIComponent, githubSafe)
const prepareDecodedUrl = flow(githubSafe, encodeURIComponent)
export function makeEncodedLink(code: string): string {
const encodedScript = encode(code)
return `${location.origin}${location.pathname}#${scriptKeyV2}=${encodedScript}`
}
export function makeExternalUrl(resourceUrl: string): string {
return `${location.origin}${
location.pathname
}#${fetchText}=${prepareDecodedUrl(resourceUrl)}`
}
export function useIdeInit(cadPackage: string) { export function useIdeInit(cadPackage: string) {
const { thunkDispatch } = useIdeContext() const { thunkDispatch } = useIdeContext()

View File

@@ -1,6 +1,7 @@
import { Popover } from '@headlessui/react' import { Popover } from '@headlessui/react'
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs' import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'
import FullScriptEncoding from 'src/components/EncodedUrl/FullScriptEncoding' import FullScriptEncoding from 'src/components/EncodedUrl/FullScriptEncoding'
import ExternalScript from 'src/components/EncodedUrl/ExternalScript'
const TopButton = ({ const TopButton = ({
onClick, onClick,
@@ -50,12 +51,11 @@ const IdeHeader = ({ handleRender }: { handleRender: () => void }) => {
className="bg-gray-300 rounded-md shadow-md overflow-hidden text-gray-700" className="bg-gray-300 rounded-md shadow-md overflow-hidden text-gray-700"
selectedTabClassName="bg-gray-200" selectedTabClassName="bg-gray-200"
> >
<TabPanel className="p-4"> <TabPanel>
<FullScriptEncoding /> <FullScriptEncoding />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<p>blah</p> <ExternalScript />
<input onPaste={(e) => console.log(e)} />
</TabPanel> </TabPanel>
<TabList className="flex whitespace-nowrap text-gray-700 border-t border-gray-700"> <TabList className="flex whitespace-nowrap text-gray-700 border-t border-gray-700">