131
app/web/src/components/EncodedUrl/ExternalScript.tsx
Normal file
131
app/web/src/components/EncodedUrl/ExternalScript.tsx
Normal 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 & 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
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user